Bitcoin¶

Import thư viện¶

In [1]:
%pip install arch seaborn
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from arch import arch_model
from arch.univariate import GARCH, ConstantMean
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
import seaborn as sns
# Thêm import cho kiểm tra tính dừng
from statsmodels.tsa.stattools import adfuller
Requirement already satisfied: arch in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (7.2.0)
Requirement already satisfied: seaborn in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (0.13.2)
Requirement already satisfied: numpy>=1.22.3 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.1.3)
Requirement already satisfied: scipy>=1.8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (1.15.3)
Requirement already satisfied: pandas>=1.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.2.3)
Requirement already satisfied: statsmodels>=0.12 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (0.14.4)
Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from seaborn) (3.10.3)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.2)
Requirement already satisfied: cycler>=0.10 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.58.1)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.8)
Requirement already satisfied: packaging>=20.0 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0)
Requirement already satisfied: pillow>=8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.2.1)
Requirement already satisfied: pyparsing>=2.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2)
Requirement already satisfied: patsy>=0.5.6 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from statsmodels>=0.12->arch) (1.0.1)
Requirement already satisfied: six>=1.5 in c:\users\hii\appdata\roaming\python\python311\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 24.0 -> 25.1.1
[notice] To update, run: python.exe -m pip install --upgrade pip

Bitcoin Dataset¶

Import csv¶

In [2]:
# Đọc file Bitcoin
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Bitcoin Historical Data.csv"

data = pd.read_csv(file_path)

# Loại bỏ dấu phẩy và chuyển đổi thành float
for col in ['Price', 'Open']:
    data[col] = data[col].str.replace(',', '', regex=False).astype(float)

# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
    val = str(val).replace(',', '').strip()
    if 'K' in val:
        return float(val.replace('K', '')) * 1_000
    elif 'M' in val:
        return float(val.replace('M', '')) * 1_000_000
    elif 'B' in val:
        return float(val.replace('B', '')) * 1_000_000_000
    else:
        return float(val)

data['Vol.'] = data['Vol.'].apply(convert_volume)

# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)

# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()

print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']

First 5 rows:
            Price   Open     Vol.
Date                             
2016-03-10  415.8  412.8  55740.0
2016-03-11  419.1  415.8  60630.0
2016-03-12  410.4  419.1  59640.0
2016-03-13  412.4  410.4  34980.0
2016-03-14  414.3  412.4  49330.0
Tổng số dữ liệu: 3370 dòng

Chia 7:3¶

Chuẩn bị dữ liệu cho GARCH¶

In [3]:
# Tính log returns cho GARCH model
data['Log_Return'] = np.log(data['Price'] / data['Price'].shift(1))
data['Simple_Return'] = data['Price'].pct_change()

# Loại bỏ giá trị NaN
data_clean = data.dropna()

print(f"Dữ liệu sau khi tính returns: {len(data_clean)} dòng")
print("\nThống kê mô tả của Log Returns:")
print(data_clean['Log_Return'].describe())

# Kiểm tra tính dừng của chuỗi returns
from statsmodels.tsa.stattools import adfuller

def check_stationarity(series, name):
    result = adfuller(series.dropna())
    print(f'\n{name} - Kiểm định ADF:')
    print(f'ADF Statistic: {result[0]:.6f}')
    print(f'p-value: {result[1]:.6f}')
    print(f'Critical Values:')
    for key, value in result[4].items():
        print(f'\t{key}: {value:.3f}')
    
    if result[1] <= 0.05:
        print("✓ Chuỗi dừng (stationary)")
    else:
        print("✗ Chuỗi không dừng (non-stationary)")
    return result[1] <= 0.05

# Kiểm tra tính dừng
is_stationary = check_stationarity(data_clean['Log_Return'], 'Log Returns')
Dữ liệu sau khi tính returns: 3369 dòng

Thống kê mô tả của Log Returns:
count    3369.000000
mean        0.001638
std         0.036633
min        -0.497278
25%        -0.012538
50%         0.001340
75%         0.017010
max         0.227602
Name: Log_Return, dtype: float64

Log Returns - Kiểm định ADF:
ADF Statistic: -40.329533
p-value: 0.000000
Critical Values:
	1%: -3.432
	5%: -2.862
	10%: -2.567
✓ Chuỗi dừng (stationary)
In [4]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data_clean) * 0.7)
train_data = data_clean.iloc[0:train_size].copy()
test_data = data_clean.iloc[train_size:].copy()

# Lấy returns cho train và test
train_returns = train_data['Log_Return'].values
test_returns = test_data['Log_Return'].values

print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
print(f"Train returns shape: {train_returns.shape}")
print(f"Test returns shape: {test_returns.shape}")
Kích thước tập train: 2358
Kích thước tập test: 1011
Train returns shape: (2358,)
Test returns shape: (1011,)

Xây dựng mô hình GARCH¶

"""
Xây dựng mô hình GARCH(p,q)

Args:
    returns_data: Chuỗi returns
    p: Order của GARCH term
    q: Order của ARCH term

Returns:
    Fitted GARCH model
"""
In [5]:
def build_garch_model(returns_data, p=1, q=1):
    # Nhân returns với 100 để có scale phù hợp
    returns_scaled = returns_data * 100
    
    # Tạo mô hình GARCH
    model = arch_model(returns_scaled, vol='Garch', p=p, q=q, dist='normal')
    
    # Fit mô hình
    fitted_model = model.fit(disp='off')
    
    return fitted_model, model

def evaluate_garch_model(fitted_model):
    """
    Đánh giá mô hình GARCH
    """
    print("="*50)
    print("THÔNG TIN MÔ HÌNH GARCH")
    print("="*50)
    print(fitted_model.summary())
    
    # Kiểm tra phần dư
    residuals = fitted_model.resid
    standardized_residuals = residuals / fitted_model.conditional_volatility
    
    # Ljung-Box test cho phần dư
    lb_result = acorr_ljungbox(residuals, lags=10, return_df=True)
    print(f"\nLjung-Box test p-value (residuals): {lb_result['lb_pvalue'].iloc[-1]:.4f}")
    
    # Ljung-Box test cho phần dư bình phương
    lb_result_sq = acorr_ljungbox(residuals**2, lags=10, return_df=True)
    print(f"Ljung-Box test p-value (squared residuals): {lb_result_sq['lb_pvalue'].iloc[-1]:.4f}")
    
    return fitted_model.aic, fitted_model.bic
In [6]:
def forecast_garch_prices(fitted_model, initial_price, forecast_horizon):
    """
    Dự đoán giá sử dụng GARCH model
    
    Args:
        fitted_model: Mô hình GARCH đã fit
        initial_price: Giá ban đầu
        forecast_horizon: Số ngày dự đoán
    
    Returns:
        Dự đoán giá và volatility
    """
    # Forecast returns và volatility
    forecast = fitted_model.forecast(horizon=forecast_horizon)
    
    # Lấy mean và variance dự đoán
    forecast_mean = forecast.mean.iloc[-1].values / 100  # Scale về decimal
    forecast_variance = forecast.variance.iloc[-1].values / 10000  # Scale về decimal
    
    # Tạo random walks cho price prediction
    np.random.seed(42)  # Để kết quả reproducible
    
    predicted_prices = []
    current_price = initial_price
    
    for i in range(forecast_horizon):
        # Sử dụng mean return và add random noise based on predicted volatility
        return_pred = forecast_mean[i] + np.random.normal(0, np.sqrt(forecast_variance[i]))
        current_price = current_price * np.exp(return_pred)
        predicted_prices.append(current_price)
    
    return np.array(predicted_prices), np.sqrt(forecast_variance) * 100
In [7]:
def plot_garch_diagnostics(fitted_model, returns_data, train_data_index):
    """
    Vẽ biểu đồ chẩn đoán cho mô hình GARCH
    """
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    
    # 1. Returns và Conditional Volatility
    # Sử dụng index từ train_data thay vì fitted_model.resid.index
    dates = train_data_index
    axes[0, 0].plot(dates, returns_data * 100, alpha=0.7, label='Returns (%)')
    axes[0, 0].plot(dates, fitted_model.conditional_volatility, color='red', label='Conditional Volatility')
    axes[0, 0].set_title('Returns và Conditional Volatility')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    axes[0, 0].tick_params(axis='x', rotation=45)
    
    # 2. Standardized Residuals
    std_resid = fitted_model.resid / fitted_model.conditional_volatility
    axes[0, 1].plot(dates, std_resid)
    axes[0, 1].set_title('Standardized Residuals')
    axes[0, 1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
    axes[0, 1].grid(True, alpha=0.3)
    axes[0, 1].tick_params(axis='x', rotation=45)
    
    # 3. Q-Q Plot
    stats.probplot(std_resid, dist="norm", plot=axes[1, 0])
    axes[1, 0].set_title('Q-Q Plot of Standardized Residuals')
    axes[1, 0].grid(True, alpha=0.3)
    
    # 4. ACF of Squared Residuals
    from statsmodels.tsa.stattools import acf
    squared_resid = std_resid ** 2
    lags = 20
    acf_vals = acf(squared_resid, nlags=lags)
    axes[1, 1].bar(range(lags+1), acf_vals, alpha=0.7)
    axes[1, 1].set_title('ACF of Squared Standardized Residuals')
    axes[1, 1].set_xlabel('Lag')
    axes[1, 1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

Huấn luyện mô hình GARCH(1,1)¶

In [8]:
# Fit mô hình GARCH(1,1) cho tập train
print("Đang huấn luyện mô hình GARCH(1,1)...")
fitted_garch, garch_model = build_garch_model(train_returns, p=1, q=1)

# Đánh giá mô hình
aic, bic = evaluate_garch_model(fitted_garch)

# Vẽ biểu đồ chẩn đoán với train_returns và train_data index
plot_garch_diagnostics(fitted_garch, train_returns, train_data.index)
Đang huấn luyện mô hình GARCH(1,1)...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -6476.88
Distribution:                  Normal   AIC:                           12961.8
Method:            Maximum Likelihood   BIC:                           12984.8
                                        No. Observations:                 2358
Date:                Tue, Jun 03 2025   Df Residuals:                     2357
Time:                        20:39:00   Df Model:                            1
                                Mean Model                                
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
mu             0.2228  7.634e-02      2.919  3.515e-03 [7.319e-02,  0.372]
                             Volatility Model                             
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
omega          0.7630      0.308      2.474  1.337e-02   [  0.158,  1.367]
alpha[1]       0.1290  4.979e-02      2.590  9.607e-03 [3.136e-02,  0.227]
beta[1]        0.8398  3.993e-02     21.031  3.437e-98   [  0.762,  0.918]
==========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0835
Ljung-Box test p-value (squared residuals): 0.0000
No description has been provided for this image

Đánh giá mô hình trên tập test¶

In [9]:
# Dự đoán trên tập test
test_size_days = len(test_data)
last_train_price = train_data['Price'].iloc[-1]

# Forecast cho test period
test_forecast = fitted_garch.forecast(horizon=test_size_days)
forecast_returns = test_forecast.mean.iloc[-1].values / 100
forecast_volatility = np.sqrt(test_forecast.variance.iloc[-1].values) / 100

# Tính predicted prices cho test set
predicted_prices_test = []
current_price = last_train_price

for i in range(test_size_days):
    # Sử dụng predicted return
    predicted_return = forecast_returns[i]
    current_price = current_price * np.exp(predicted_return)
    predicted_prices_test.append(current_price)

predicted_prices_test = np.array(predicted_prices_test)

# Tính metrics
actual_test_prices = test_data['Price'].values
mape = mean_absolute_percentage_error(actual_test_prices, predicted_prices_test)
mse = mean_squared_error(actual_test_prices, predicted_prices_test)
rmse = np.sqrt(mse)

print(f'Kết quả đánh giá mô hình GARCH(1,1):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'AIC: {aic:.2f}')
print(f'BIC: {bic:.2f}')

# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data.index, actual_test_prices, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (7:3)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1):
MAPE: 0.55%
MSE: 1572132497.08
RMSE: 39650.13
AIC: 12961.76
BIC: 12984.82
No description has been provided for this image

Dự đoán tương lai¶

In [10]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_price = data_clean['Price'].iloc[-1]

# Forecast prices
forecast_30, vol_30 = forecast_garch_prices(fitted_garch, last_price, 30)
forecast_60, vol_60 = forecast_garch_prices(fitted_garch, last_price, 60)
forecast_90, vol_90 = forecast_garch_prices(fitted_garch, last_price, 90)

# Tạo dates cho forecasts
forecast_dates_30 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

# Tạo DataFrames
forecast_df_30 = pd.DataFrame(forecast_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecast_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecast_90, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa
plt.figure(figsize=(16, 10))

# Vẽ dữ liệu lịch sử
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán test
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán trên test set', color='orange', linewidth=2, alpha=0.8)

# Vẽ dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'], 
         label='Dự đoán 30 ngày', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'], 
         label='Dự đoán 60 ngày', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'], 
         label='Dự đoán 90 ngày', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Đường phân cách
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')

plt.title('Dự đoán giá Bitcoin bằng GARCH(1,1) - Split 7:3', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
In [11]:
# For the 7:3 split, we're using GARCH(1,1) model
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 7:3):')
print(f'Giá cao nhất: ${forecast_30.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30.min():.2f}')
print(f'Giá trung bình: ${forecast_30.mean():.2f}')
print(f'Volatility trung bình: {vol_30.mean():.2f}%')
Dự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 7:3):
Giá cao nhất: $125180.21
Giá thấp nhất: $85620.35
Giá trung bình: $105057.66
Volatility trung bình: 4.07%

Chia 8:2¶

Chuẩn bị dữ liệu 8:2¶

In [12]:
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
In [13]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data_clean) * 0.8)
train_data_82 = data_clean.iloc[0:train_size_82].copy()
test_data_82 = data_clean.iloc[train_size_82:].copy()

# Lấy returns cho train và test
train_returns_82 = train_data_82['Log_Return'].values
test_returns_82 = test_data_82['Log_Return'].values

print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2695
Kích thước tập test 8:2: 674

Huấn luyện mô hình GARCH(1,1) cho 8:2¶

In [14]:
# Fit mô hình GARCH(1,1) cho split 8:2
print("Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...")
fitted_garch_82, garch_model_82 = build_garch_model(train_returns_82, p=1, q=1)

# Đánh giá mô hình
aic_82, bic_82 = evaluate_garch_model(fitted_garch_82)
Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -7273.22
Distribution:                  Normal   AIC:                           14554.4
Method:            Maximum Likelihood   BIC:                           14578.0
                                        No. Observations:                 2695
Date:                Tue, Jun 03 2025   Df Residuals:                     2694
Time:                        20:39:01   Df Model:                            1
                                Mean Model                                
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
mu             0.1954  6.449e-02      3.030  2.444e-03 [6.903e-02,  0.322]
                             Volatility Model                             
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
omega          0.7005      0.245      2.856  4.290e-03   [  0.220,  1.181]
alpha[1]       0.1464  4.761e-02      3.076  2.096e-03 [5.314e-02,  0.240]
beta[1]        0.8262  3.423e-02     24.138 9.952e-129   [  0.759,  0.893]
==========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0936
Ljung-Box test p-value (squared residuals): 0.0000

Đánh giá mô hình 8:2¶

In [15]:
# Dự đoán trên tập test 8:2
test_size_days_82 = len(test_data_82)
last_train_price_82 = train_data_82['Price'].iloc[-1]

# Forecast cho test period
test_forecast_82 = fitted_garch_82.forecast(horizon=test_size_days_82)
forecast_returns_82 = test_forecast_82.mean.iloc[-1].values / 100

# Tính predicted prices cho test set 8:2
predicted_prices_test_82 = []
current_price_82 = last_train_price_82

for i in range(test_size_days_82):
    predicted_return = forecast_returns_82[i]
    current_price_82 = current_price_82 * np.exp(predicted_return)
    predicted_prices_test_82.append(current_price_82)

predicted_prices_test_82 = np.array(predicted_prices_test_82)

# Tính metrics cho 8:2
actual_test_prices_82 = test_data_82['Price'].values
mape_82 = mean_absolute_percentage_error(actual_test_prices_82, predicted_prices_test_82)
mse_82 = mean_squared_error(actual_test_prices_82, predicted_prices_test_82)
rmse_82 = np.sqrt(mse_82)

print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'AIC: {aic_82:.2f}')
print(f'BIC: {bic_82:.2f}')

# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data_82.index, actual_test_prices_82, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (8:2)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:
MAPE: 0.13%
MSE: 119564962.50
RMSE: 10934.58
AIC: 14554.44
BIC: 14578.03
No description has been provided for this image

Dự đoán tương lai 8:2¶

In [16]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho 8:2
forecast_30_82, vol_30_82 = forecast_garch_prices(fitted_garch_82, last_price, 30)
forecast_60_82, vol_60_82 = forecast_garch_prices(fitted_garch_82, last_price, 60)
forecast_90_82, vol_90_82 = forecast_garch_prices(fitted_garch_82, last_price, 90)

# Tạo DataFrames cho 8:2
forecast_df_30_82 = pd.DataFrame(forecast_30_82, index=forecast_dates_30, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecast_60_82, index=forecast_dates_60, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecast_90_82, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa 8:2
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán trên test set (8:2)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'], 
         label='Dự đoán 30 ngày (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'], 
         label='Dự đoán 60 ngày (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'], 
         label='Dự đoán 90 ngày (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Bitcoin bằng GARCH(1,1) - Split 8:2', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 8:2):')
print(f'Giá cao nhất: ${forecast_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_82.min():.2f}')
print(f'Giá trung bình: ${forecast_30_82.mean():.2f}')
print(f'Volatility trung bình: {vol_30_82.mean():.2f}%')
No description has been provided for this image
Dự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 8:2):
Giá cao nhất: $119449.90
Giá thấp nhất: $86091.68
Giá trung bình: $103118.90
Volatility trung bình: 3.39%

Chia 9:1¶

Chuẩn bị dữ liệu 9:1¶

In [17]:
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
In [18]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data_clean) * 0.9)
train_data_91 = data_clean.iloc[0:train_size_91].copy()
test_data_91 = data_clean.iloc[train_size_91:].copy()

# Lấy returns cho train và test
train_returns_91 = train_data_91['Log_Return'].values
test_returns_91 = test_data_91['Log_Return'].values

print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3032
Kích thước tập test 9:1: 337

Huấn luyện mô hình GARCH(1,1) cho 9:1¶

In [19]:
# Fit mô hình GARCH(1,1) cho split 9:1
print("Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...")
fitted_garch_91, garch_model_91 = build_garch_model(train_returns_91, p=1, q=1)

# Đánh giá mô hình
aic_91, bic_91 = evaluate_garch_model(fitted_garch_91)
Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -8062.68
Distribution:                  Normal   AIC:                           16133.4
Method:            Maximum Likelihood   BIC:                           16157.4
                                        No. Observations:                 3032
Date:                Tue, Jun 03 2025   Df Residuals:                     3031
Time:                        20:39:02   Df Model:                            1
                                Mean Model                                
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
mu             0.1932  5.804e-02      3.330  8.694e-04 [7.949e-02,  0.307]
                             Volatility Model                             
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
omega          0.6252      0.208      3.003  2.678e-03   [  0.217,  1.033]
alpha[1]       0.1420  4.572e-02      3.107  1.891e-03 [5.244e-02,  0.232]
beta[1]        0.8308  3.478e-02     23.885 4.393e-126   [  0.763,  0.899]
==========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0554
Ljung-Box test p-value (squared residuals): 0.0000

Đánh giá mô hình 9:1¶

In [20]:
# Dự đoán trên tập test 9:1
test_size_days_91 = len(test_data_91)
last_train_price_91 = train_data_91['Price'].iloc[-1]

# Forecast cho test period
test_forecast_91 = fitted_garch_91.forecast(horizon=test_size_days_91)
forecast_returns_91 = test_forecast_91.mean.iloc[-1].values / 100

# Tính predicted prices cho test set 9:1
predicted_prices_test_91 = []
current_price_91 = last_train_price_91

for i in range(test_size_days_91):
    predicted_return = forecast_returns_91[i]
    current_price_91 = current_price_91 * np.exp(predicted_return)
    predicted_prices_test_91.append(current_price_91)

predicted_prices_test_91 = np.array(predicted_prices_test_91)

# Tính metrics cho 9:1
actual_test_prices_91 = test_data_91['Price'].values
mape_91 = mean_absolute_percentage_error(actual_test_prices_91, predicted_prices_test_91)
mse_91 = mean_squared_error(actual_test_prices_91, predicted_prices_test_91)
rmse_91 = np.sqrt(mse_91)

print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'AIC: {aic_91:.2f}')
print(f'BIC: {bic_91:.2f}')

# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data_91.index, actual_test_prices_91, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (9:1)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:
MAPE: 0.13%
MSE: 136029586.18
RMSE: 11663.17
AIC: 16133.35
BIC: 16157.42
No description has been provided for this image

Dự đoán tương lai 9:1¶

In [21]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho 9:1
forecast_30_91, vol_30_91 = forecast_garch_prices(fitted_garch_91, last_price, 30)
forecast_60_91, vol_60_91 = forecast_garch_prices(fitted_garch_91, last_price, 60)
forecast_90_91, vol_90_91 = forecast_garch_prices(fitted_garch_91, last_price, 90)

# Tạo DataFrames cho 9:1
forecast_df_30_91 = pd.DataFrame(forecast_30_91, index=forecast_dates_30, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecast_60_91, index=forecast_dates_60, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecast_90_91, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa 9:1
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán trên test set (9:1)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'], 
         label='Dự đoán 30 ngày (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'], 
         label='Dự đoán 60 ngày (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'], 
         label='Dự đoán 90 ngày (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Bitcoin bằng GARCH(1,1) - Split 9:1', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 9:1):')
print(f'Giá cao nhất: ${forecast_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_91.min():.2f}')
print(f'Giá trung bình: ${forecast_30_91.mean():.2f}')
print(f'Volatility trung bình: {vol_30_91.mean():.2f}%')
No description has been provided for this image
Dự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 9:1):
Giá cao nhất: $120899.64
Giá thấp nhất: $86736.24
Giá trung bình: $103922.23
Volatility trung bình: 3.49%

So sánh 3 tỉ lệ GARCH(1,1)¶

In [22]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu cho GARCH(1,1)
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH(1,1) MODEL")
print("="*80)

# Thu thập thông tin từ 3 splits
garch_splits_info = {
    '7:3': {
        'train_size': len(train_data),
        'test_size': len(test_data),
        'mape': mape,
        'mse': mse,
        'rmse': rmse,
        'aic': aic,
        'bic': bic,
        'avg_volatility': vol_30.mean()
    },
    '8:2': {
        'train_size': len(train_data_82),
        'test_size': len(test_data_82),
        'mape': mape_82,
        'mse': mse_82,
        'rmse': rmse_82,
        'aic': aic_82,
        'bic': bic_82,
        'avg_volatility': vol_30_82.mean()
    },
    '9:1': {
        'train_size': len(train_data_91),
        'test_size': len(test_data_91),
        'mape': mape_91,
        'mse': mse_91,
        'rmse': rmse_91,
        'aic': aic_91,
        'bic': bic_91,
        'avg_volatility': vol_30_91.mean()
    }
}

# In bảng so sánh GARCH
for split, info in garch_splits_info.items():
    print(f"\n{split} Split (GARCH(1,1)):")
    print(f"  Kích thước train: {info['train_size']:,} mẫu")
    print(f"  Kích thước test: {info['test_size']:,} mẫu")
    print(f"  MAPE: {info['mape']:.2f}%")
    print(f"  MSE: {info['mse']:,.2f}")
    print(f"  RMSE: {info['rmse']:,.2f}")
    print(f"  AIC: {info['aic']:.2f}")
    print(f"  BIC: {info['bic']:.2f}")
    print(f"  Avg Volatility: {info['avg_volatility']:.2f}%")
================================================================================
SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH(1,1) MODEL
================================================================================

7:3 Split (GARCH(1,1)):
  Kích thước train: 2,358 mẫu
  Kích thước test: 1,011 mẫu
  MAPE: 0.55%
  MSE: 1,572,132,497.08
  RMSE: 39,650.13
  AIC: 12961.76
  BIC: 12984.82
  Avg Volatility: 4.07%

8:2 Split (GARCH(1,1)):
  Kích thước train: 2,695 mẫu
  Kích thước test: 674 mẫu
  MAPE: 0.13%
  MSE: 119,564,962.50
  RMSE: 10,934.58
  AIC: 14554.44
  BIC: 14578.03
  Avg Volatility: 3.39%

9:1 Split (GARCH(1,1)):
  Kích thước train: 3,032 mẫu
  Kích thước test: 337 mẫu
  MAPE: 0.13%
  MSE: 136,029,586.18
  RMSE: 11,663.17
  AIC: 16133.35
  BIC: 16157.42
  Avg Volatility: 3.49%
In [23]:
# Vẽ biểu đồ so sánh các metrics cho GARCH(1,1)
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']

# 1. So sánh MAPE
mape_values = [garch_splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%) - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
    axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 2. So sánh RMSE
rmse_values = [garch_splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD) - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
    axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')

# 3. So sánh AIC
aic_values = [garch_splits_info[split]['aic'] for split in splits]
axes[0, 2].bar(splits, aic_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh AIC - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('AIC')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(aic_values):
    axes[0, 2].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')

# 4. So sánh BIC
bic_values = [garch_splits_info[split]['bic'] for split in splits]
axes[1, 0].bar(splits, bic_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh BIC - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('BIC')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(bic_values):
    axes[1, 0].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')

# 5. So sánh Average Volatility
vol_values = [garch_splits_info[split]['avg_volatility'] for split in splits]
axes[1, 1].bar(splits, vol_values, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Avg Volatility (%) - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('Volatility (%)')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(vol_values):
    axes[1, 1].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 6. So sánh kích thước test set
test_sizes = [garch_splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
    axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [24]:
# Tạo DataFrame tổng hợp kết quả GARCH
garch_comparison_df = pd.DataFrame({
    'Split': ['7:3', '8:2', '9:1'],
    'Train_Size': [garch_splits_info[split]['train_size'] for split in ['7:3', '8:2', '9:1']],
    'Test_Size': [garch_splits_info[split]['test_size'] for split in ['7:3', '8:2', '9:1']],
    'MAPE (%)': [garch_splits_info[split]['mape'] for split in ['7:3', '8:2', '9:1']],
    'RMSE (USD)': [garch_splits_info[split]['rmse'] for split in ['7:3', '8:2', '9:1']],
    'MSE': [garch_splits_info[split]['mse'] for split in ['7:3', '8:2', '9:1']],
    'AIC': [garch_splits_info[split]['aic'] for split in ['7:3', '8:2', '9:1']],
    'BIC': [garch_splits_info[split]['bic'] for split in ['7:3', '8:2', '9:1']],
    'Avg_Volatility (%)': [garch_splits_info[split]['avg_volatility'] for split in ['7:3', '8:2', '9:1']]
})

print("\nBẢNG TỔNG HỢP KẾT QUẢ GARCH(1,1):")
print("="*100)
print(garch_comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ GARCH(1,1):
====================================================================================================
Split  Train_Size  Test_Size  MAPE (%)  RMSE (USD)             MSE        AIC        BIC  Avg_Volatility (%)
  7:3        2358       1011    0.5514  39650.1261 1572132497.0754 12961.7608 12984.8231              4.0730
  8:2        2695        674    0.1299  10934.5765  119564962.5033 14554.4358 14578.0325              3.3878
  9:1        3032        337    0.1255  11663.1722  136029586.1774 16133.3505 16157.4184              3.4942
In [25]:
# Phân tích và đưa ra khuyến nghị cho GARCH
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH(1,1) MODEL")
print("="*80)

splits = ['7:3', '8:2', '9:1']

# Tìm split tốt nhất cho từng metric
best_mape_split_garch = splits[np.argmin([garch_splits_info[split]['mape'] for split in splits])]
best_rmse_split_garch = splits[np.argmin([garch_splits_info[split]['rmse'] for split in splits])]
best_aic_split_garch = splits[np.argmin([garch_splits_info[split]['aic'] for split in splits])]
best_bic_split_garch = splits[np.argmin([garch_splits_info[split]['bic'] for split in splits])]

print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH(1,1)):")
print(f"   • Tốt nhất theo MAPE: {best_mape_split_garch} ({garch_splits_info[best_mape_split_garch]['mape']:.2f}%)")
print(f"   • Tốt nhất theo RMSE: {best_rmse_split_garch} ({garch_splits_info[best_rmse_split_garch]['rmse']:,.2f} USD)")
print(f"   • Tốt nhất theo AIC: {best_aic_split_garch} ({garch_splits_info[best_aic_split_garch]['aic']:.2f})")
print(f"   • Tốt nhất theo BIC: {best_bic_split_garch} ({garch_splits_info[best_bic_split_garch]['bic']:.2f})")

# Tính điểm tổng hợp cho GARCH
def calculate_garch_rank_score(splits_info):
    scores = {}
    splits_list = list(splits_info.keys())
    
    # Rank cho các metrics (thấp hơn = tốt hơn)
    mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
    rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
    aic_rank = sorted(splits_list, key=lambda x: splits_info[x]['aic'])
    bic_rank = sorted(splits_list, key=lambda x: splits_info[x]['bic'])
    
    for split in splits_list:
        # Điểm rank với trọng số cho GARCH
        score = (mape_rank.index(split) + 1) * 0.35 + \
                (rmse_rank.index(split) + 1) * 0.35 + \
                (aic_rank.index(split) + 1) * 0.15 + \
                (bic_rank.index(split) + 1) * 0.15
        scores[split] = score
    
    return scores

# Tính điểm tổng hợp
garch_scores = calculate_garch_rank_score(garch_splits_info)
best_overall_split_garch = min(garch_scores, key=garch_scores.get)

print(f"\n2. ĐIỂM TỔNG HỢP GARCH(1,1) (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):")
for split in garch_splits_info.keys():
    print(f"   • {split}: {garch_scores[split]:.2f} điểm")

print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH(1,1):")
print(f"   🏆 MÔ HÌNH GARCH(1,1) TỐT NHẤT: Split {best_overall_split_garch}")
print(f"   📊 Lý do:")
print(f"      - MAPE: {garch_splits_info[best_overall_split_garch]['mape']:.2f}%")
print(f"      - RMSE: {garch_splits_info[best_overall_split_garch]['rmse']:,.2f} USD")
print(f"      - AIC: {garch_splits_info[best_overall_split_garch]['aic']:.2f}")
print(f"      - BIC: {garch_splits_info[best_overall_split_garch]['bic']:.2f}")
print(f"      - Avg Volatility: {garch_splits_info[best_overall_split_garch]['avg_volatility']:.2f}%")
print(f"      - Tập test có {garch_splits_info[best_overall_split_garch]['test_size']:,} mẫu")

print(f"\n4. NHẬN XÉT VỀ GARCH(1,1) MODEL:")
print("   • GARCH(1,1) là mô hình chuẩn được sử dụng rộng rãi")
print("   • Hiệu quả trong việc mô hình hóa volatility clustering")
print("   • Phù hợp với dữ liệu tài chính có tính heteroskedasticity")
print("   • Cung cấp forecast về volatility cùng với price prediction")
print("   • AIC và BIC giúp đánh giá model fit và complexity")
print("   • Đã kiểm tra tính dừng của chuỗi returns trước khi áp dụng")

print(f"\n   ✅ ƯU ĐIỂM GARCH(1,1):")
print("   • Đơn giản, dễ hiểu và implement")
print("   • Ít tham số, tránh overfitting")
print("   • Được chấp nhận rộng rãi trong cộng đồng tài chính")
print("   • Phù hợp cho short-term forecasting với dữ liệu có volatility cao như Bitcoin")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH(1,1) MODEL
================================================================================

1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH(1,1)):
   • Tốt nhất theo MAPE: 9:1 (0.13%)
   • Tốt nhất theo RMSE: 8:2 (10,934.58 USD)
   • Tốt nhất theo AIC: 7:3 (12961.76)
   • Tốt nhất theo BIC: 7:3 (12984.82)

2. ĐIỂM TỔNG HỢP GARCH(1,1) (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):
   • 7:3: 2.40 điểm
   • 8:2: 1.65 điểm
   • 9:1: 1.95 điểm

3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH(1,1):
   🏆 MÔ HÌNH GARCH(1,1) TỐT NHẤT: Split 8:2
   📊 Lý do:
      - MAPE: 0.13%
      - RMSE: 10,934.58 USD
      - AIC: 14554.44
      - BIC: 14578.03
      - Avg Volatility: 3.39%
      - Tập test có 674 mẫu

4. NHẬN XÉT VỀ GARCH(1,1) MODEL:
   • GARCH(1,1) là mô hình chuẩn được sử dụng rộng rãi
   • Hiệu quả trong việc mô hình hóa volatility clustering
   • Phù hợp với dữ liệu tài chính có tính heteroskedasticity
   • Cung cấp forecast về volatility cùng với price prediction
   • AIC và BIC giúp đánh giá model fit và complexity
   • Đã kiểm tra tính dừng của chuỗi returns trước khi áp dụng

   ✅ ƯU ĐIỂM GARCH(1,1):
   • Đơn giản, dễ hiểu và implement
   • Ít tham số, tránh overfitting
   • Được chấp nhận rộng rãi trong cộng đồng tài chính
   • Phù hợp cho short-term forecasting với dữ liệu có volatility cao như Bitcoin

ETH¶

Import thư viện¶

In [26]:
%pip install arch seaborn
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from arch import arch_model
from arch.univariate import GARCH, ConstantMean
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
import seaborn as sns
# Thêm import cho kiểm tra tính dừng
from statsmodels.tsa.stattools import adfuller
Requirement already satisfied: arch in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (7.2.0)
Requirement already satisfied: seaborn in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (0.13.2)
Requirement already satisfied: numpy>=1.22.3 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.1.3)
Requirement already satisfied: scipy>=1.8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (1.15.3)
Requirement already satisfied: pandas>=1.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.2.3)
Requirement already satisfied: statsmodels>=0.12 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (0.14.4)
Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from seaborn) (3.10.3)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.2)
Requirement already satisfied: cycler>=0.10 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.58.1)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.8)
Requirement already satisfied: packaging>=20.0 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0)
Requirement already satisfied: pillow>=8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.2.1)
Requirement already satisfied: pyparsing>=2.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2)
Requirement already satisfied: patsy>=0.5.6 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from statsmodels>=0.12->arch) (1.0.1)
Requirement already satisfied: six>=1.5 in c:\users\hii\appdata\roaming\python\python311\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 24.0 -> 25.1.1
[notice] To update, run: python.exe -m pip install --upgrade pip

ETH Dataset¶

Import csv¶

In [27]:
# Đọc file ETH
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Ethereum Historical Data.csv"

data = pd.read_csv(file_path)

# Loại bỏ dấu phẩy và chuyển đổi thành float cho Price và Open
for col in ['Price', 'Open']:
    data[col] = data[col].str.replace(',', '', regex=False).astype(float)

# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B' thành số thực
def convert_volume(val):
    val = str(val).replace(',', '').strip()
    if 'K' in val:
        return float(val.replace('K', '')) * 1_000
    elif 'M' in val:
        return float(val.replace('M', '')) * 1_000_000
    elif 'B' in val:
        return float(val.replace('B', '')) * 1_000_000_000
    else:
        try:
            return float(val)
        except ValueError:
            return np.nan  # Trường hợp val là '' hoặc không chuyển được

data['Vol.'] = data['Vol.'].apply(convert_volume)

# Kiểm tra NaN ban đầu trong Vol.
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Nội suy giá trị Vol. (chỉ sau khi đã convert sang số)
data['Vol.'] = data['Vol.'].interpolate(method='linear')

# Điền 0 cho NaN còn lại
data['Vol.'] = data['Vol.'].fillna(0)

# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)

# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()

print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 8
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']

First 5 rows:
            Price   Open     Vol.
Date                             
2016-03-10  11.75  11.20      0.0
2016-03-11  11.95  11.75    180.0
2016-03-12  12.92  11.95    830.0
2016-03-13  15.07  12.92   1300.0
2016-03-14  12.50  15.07  92180.0
Tổng số dữ liệu: 3370 dòng

Chia 7:3¶

Chuẩn bị dữ liệu cho GARCH¶

In [28]:
# Tính log returns cho GARCH model
data['Log_Return'] = np.log(data['Price'] / data['Price'].shift(1))
data['Simple_Return'] = data['Price'].pct_change()

# Loại bỏ giá trị NaN
data_clean = data.dropna()

print(f"Dữ liệu sau khi tính returns: {len(data_clean)} dòng")
print("\nThống kê mô tả của Log Returns:")
print(data_clean['Log_Return'].describe())

# Kiểm tra tính dừng của chuỗi returns
from statsmodels.tsa.stattools import adfuller

def check_stationarity(series, name):
    result = adfuller(series.dropna())
    print(f'\n{name} - Kiểm định ADF:')
    print(f'ADF Statistic: {result[0]:.6f}')
    print(f'p-value: {result[1]:.6f}')
    print(f'Critical Values:')
    for key, value in result[4].items():
        print(f'\t{key}: {value:.3f}')
    
    if result[1] <= 0.05:
        print("✓ Chuỗi dừng (stationary)")
    else:
        print("✗ Chuỗi không dừng (non-stationary)")
    return result[1] <= 0.05

# Kiểm tra tính dừng
is_stationary = check_stationarity(data_clean['Log_Return'], 'Log Returns')
Dữ liệu sau khi tính returns: 3369 dòng

Thống kê mô tả của Log Returns:
count    3369.000000
mean        0.001593
std         0.050663
min        -0.589639
25%        -0.020598
50%         0.000708
75%         0.023597
max         0.258599
Name: Log_Return, dtype: float64

Log Returns - Kiểm định ADF:
ADF Statistic: -32.078477
p-value: 0.000000
Critical Values:
	1%: -3.432
	5%: -2.862
	10%: -2.567
✓ Chuỗi dừng (stationary)
In [29]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data_clean) * 0.7)
train_data = data_clean.iloc[0:train_size].copy()
test_data = data_clean.iloc[train_size:].copy()

# Lấy returns cho train và test
train_returns = train_data['Log_Return'].values
test_returns = test_data['Log_Return'].values

print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
print(f"Train returns shape: {train_returns.shape}")
print(f"Test returns shape: {test_returns.shape}")
Kích thước tập train: 2358
Kích thước tập test: 1011
Train returns shape: (2358,)
Test returns shape: (1011,)

Xây dựng mô hình GARCH¶

In [30]:
def build_garch_model(returns_data, p=1, q=1):
    """
    Xây dựng mô hình GARCH(p,q)
    
    Args:
        returns_data: Chuỗi returns
        p: Order của GARCH term
        q: Order của ARCH term
    
    Returns:
        Fitted GARCH model
    """
    # Nhân returns với 100 để có scale phù hợp
    returns_scaled = returns_data * 100
    
    # Tạo mô hình GARCH
    model = arch_model(returns_scaled, vol='Garch', p=p, q=q, dist='normal')
    
    # Fit mô hình
    fitted_model = model.fit(disp='off')
    
    return fitted_model, model

def evaluate_garch_model(fitted_model):
    """
    Đánh giá mô hình GARCH
    """
    print("="*50)
    print("THÔNG TIN MÔ HÌNH GARCH")
    print("="*50)
    print(fitted_model.summary())
    
    # Kiểm tra phần dư
    residuals = fitted_model.resid
    standardized_residuals = residuals / fitted_model.conditional_volatility
    
    # Ljung-Box test cho phần dư
    lb_result = acorr_ljungbox(residuals, lags=10, return_df=True)
    print(f"\nLjung-Box test p-value (residuals): {lb_result['lb_pvalue'].iloc[-1]:.4f}")
    
    # Ljung-Box test cho phần dư bình phương
    lb_result_sq = acorr_ljungbox(residuals**2, lags=10, return_df=True)
    print(f"Ljung-Box test p-value (squared residuals): {lb_result_sq['lb_pvalue'].iloc[-1]:.4f}")
    
    return fitted_model.aic, fitted_model.bic
In [31]:
def forecast_garch_prices(fitted_model, initial_price, forecast_horizon):
    """
    Dự đoán giá sử dụng GARCH model
    
    Args:
        fitted_model: Mô hình GARCH đã fit
        initial_price: Giá ban đầu
        forecast_horizon: Số ngày dự đoán
    
    Returns:
        Dự đoán giá và volatility
    """
    # Forecast returns và volatility
    forecast = fitted_model.forecast(horizon=forecast_horizon)
    
    # Lấy mean và variance dự đoán
    forecast_mean = forecast.mean.iloc[-1].values / 100  # Scale về decimal
    forecast_variance = forecast.variance.iloc[-1].values / 10000  # Scale về decimal
    
    # Tạo random walks cho price prediction
    np.random.seed(42)  # Để kết quả reproducible
    
    predicted_prices = []
    current_price = initial_price
    
    for i in range(forecast_horizon):
        # Sử dụng mean return và add random noise based on predicted volatility
        return_pred = forecast_mean[i] + np.random.normal(0, np.sqrt(forecast_variance[i]))
        current_price = current_price * np.exp(return_pred)
        predicted_prices.append(current_price)
    
    return np.array(predicted_prices), np.sqrt(forecast_variance) * 100
In [32]:
def plot_garch_diagnostics(fitted_model, returns_data, train_data_index):
    """
    Vẽ biểu đồ chẩn đoán cho mô hình GARCH
    """
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    
    # 1. Returns và Conditional Volatility
    # Sử dụng index từ train_data thay vì fitted_model.resid.index
    dates = train_data_index
    axes[0, 0].plot(dates, returns_data * 100, alpha=0.7, label='Returns (%)')
    axes[0, 0].plot(dates, fitted_model.conditional_volatility, color='red', label='Conditional Volatility')
    axes[0, 0].set_title('Returns và Conditional Volatility')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    axes[0, 0].tick_params(axis='x', rotation=45)
    
    # 2. Standardized Residuals
    std_resid = fitted_model.resid / fitted_model.conditional_volatility
    axes[0, 1].plot(dates, std_resid)
    axes[0, 1].set_title('Standardized Residuals')
    axes[0, 1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
    axes[0, 1].grid(True, alpha=0.3)
    axes[0, 1].tick_params(axis='x', rotation=45)
    
    # 3. Q-Q Plot
    stats.probplot(std_resid, dist="norm", plot=axes[1, 0])
    axes[1, 0].set_title('Q-Q Plot of Standardized Residuals')
    axes[1, 0].grid(True, alpha=0.3)
    
    # 4. ACF of Squared Residuals
    from statsmodels.tsa.stattools import acf
    squared_resid = std_resid ** 2
    lags = 20
    acf_vals = acf(squared_resid, nlags=lags)
    axes[1, 1].bar(range(lags+1), acf_vals, alpha=0.7)
    axes[1, 1].set_title('ACF of Squared Standardized Residuals')
    axes[1, 1].set_xlabel('Lag')
    axes[1, 1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

Huấn luyện mô hình GARCH¶

In [33]:
# Fit mô hình GARCH(1,1) cho tập train
print("Đang huấn luyện mô hình GARCH(1,1)...")
fitted_garch, garch_model = build_garch_model(train_returns, p=1, q=1)

# Đánh giá mô hình
aic, bic = evaluate_garch_model(fitted_garch)

# Vẽ biểu đồ chẩn đoán với train_returns và train_data index
plot_garch_diagnostics(fitted_garch, train_returns, train_data.index)
Đang huấn luyện mô hình GARCH(1,1)...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -7268.61
Distribution:                  Normal   AIC:                           14545.2
Method:            Maximum Likelihood   BIC:                           14568.3
                                        No. Observations:                 2358
Date:                Tue, Jun 03 2025   Df Residuals:                     2357
Time:                        20:39:06   Df Model:                            1
                                 Mean Model                                
===========================================================================
                 coef    std err          t      P>|t|     95.0% Conf. Int.
---------------------------------------------------------------------------
mu             0.1753      0.100      1.748  8.050e-02 [-2.128e-02,  0.372]
                             Volatility Model                             
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
omega          2.2080      0.846      2.610  9.052e-03   [  0.550,  3.866]
alpha[1]       0.1239  3.341e-02      3.708  2.088e-04 [5.840e-02,  0.189]
beta[1]        0.8115  4.769e-02     17.016  6.253e-65   [  0.718,  0.905]
==========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0058
Ljung-Box test p-value (squared residuals): 0.0000
No description has been provided for this image

Đánh giá mô hình trên tập test¶

In [34]:
# Dự đoán trên tập test
test_size_days = len(test_data)
last_train_price = train_data['Price'].iloc[-1]

# Forecast cho test period
test_forecast = fitted_garch.forecast(horizon=test_size_days)
forecast_returns = test_forecast.mean.iloc[-1].values / 100
forecast_volatility = np.sqrt(test_forecast.variance.iloc[-1].values) / 100

# Tính predicted prices cho test set
predicted_prices_test = []
current_price = last_train_price

for i in range(test_size_days):
    # Sử dụng predicted return
    predicted_return = forecast_returns[i]
    current_price = current_price * np.exp(predicted_return)
    predicted_prices_test.append(current_price)

predicted_prices_test = np.array(predicted_prices_test)

# Tính metrics
actual_test_prices = test_data['Price'].values
mape = mean_absolute_percentage_error(actual_test_prices, predicted_prices_test)
mse = mean_squared_error(actual_test_prices, predicted_prices_test)
rmse = np.sqrt(mse)

print(f'Kết quả đánh giá mô hình GARCH(1,1):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'AIC: {aic:.2f}')
print(f'BIC: {bic:.2f}')

# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data.index, actual_test_prices, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (7:3)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1):
MAPE: 1.01%
MSE: 9081335.20
RMSE: 3013.53
AIC: 14545.22
BIC: 14568.28
No description has been provided for this image

Dự đoán tương lai¶

In [35]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_price = data_clean['Price'].iloc[-1]

# Forecast prices
forecast_30, vol_30 = forecast_garch_prices(fitted_garch, last_price, 30)
forecast_60, vol_60 = forecast_garch_prices(fitted_garch, last_price, 60)
forecast_90, vol_90 = forecast_garch_prices(fitted_garch, last_price, 90)

# Tạo dates cho forecasts
forecast_dates_30 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

# Tạo DataFrames
forecast_df_30 = pd.DataFrame(forecast_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecast_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecast_90, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa
plt.figure(figsize=(16, 10))

# Vẽ dữ liệu lịch sử
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán test
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán trên test set', color='orange', linewidth=2, alpha=0.8)

# Vẽ dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'], 
         label='Dự đoán 30 ngày', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'], 
         label='Dự đoán 60 ngày', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'], 
         label='Dự đoán 90 ngày', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Đường phân cách
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')

plt.title('Dự đoán giá Ethereum bằng GARCH(1,1) - Split 7:3', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
In [36]:
print(f'\nDự đoán giá Ethereum 30 ngày tiếp theo (GARCH 7:3):')
print(f'Giá cao nhất: ${forecast_30.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30.min():.2f}')
print(f'Giá trung bình: ${forecast_30.mean():.2f}')
print(f'Volatility trung bình: {vol_30.mean():.2f}%')
Dự đoán giá Ethereum 30 ngày tiếp theo (GARCH 7:3):
Giá cao nhất: $3216.78
Giá thấp nhất: $1904.24
Giá trung bình: $2542.63
Volatility trung bình: 5.39%

Chia 8:2¶

Chuẩn bị dữ liệu 8:2¶

In [37]:
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
In [38]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data_clean) * 0.8)
train_data_82 = data_clean.iloc[0:train_size_82].copy()
test_data_82 = data_clean.iloc[train_size_82:].copy()

# Lấy returns cho train và test
train_returns_82 = train_data_82['Log_Return'].values
test_returns_82 = test_data_82['Log_Return'].values

print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2695
Kích thước tập test 8:2: 674

Huấn luyện mô hình GARCH 8:2¶

In [39]:
# Fit mô hình GARCH(1,1) cho split 8:2
print("Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...")
fitted_garch_82, garch_model_82 = build_garch_model(train_returns_82, p=1, q=1)

# Đánh giá mô hình
aic_82, bic_82 = evaluate_garch_model(fitted_garch_82)
Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -8145.50
Distribution:                  Normal   AIC:                           16299.0
Method:            Maximum Likelihood   BIC:                           16322.6
                                        No. Observations:                 2695
Date:                Tue, Jun 03 2025   Df Residuals:                     2694
Time:                        20:39:08   Df Model:                            1
                                 Mean Model                                
===========================================================================
                 coef    std err          t      P>|t|     95.0% Conf. Int.
---------------------------------------------------------------------------
mu             0.1537  8.709e-02      1.765  7.763e-02 [-1.701e-02,  0.324]
                             Volatility Model                             
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
omega          1.2652      0.564      2.243  2.493e-02   [  0.159,  2.371]
alpha[1]       0.1237  3.420e-02      3.619  2.960e-04 [5.672e-02,  0.191]
beta[1]        0.8401  4.303e-02     19.523  6.957e-85   [  0.756,  0.924]
==========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0051
Ljung-Box test p-value (squared residuals): 0.0000

Đánh giá mô hình 8:2¶

In [40]:
# Dự đoán trên tập test 8:2
test_size_days_82 = len(test_data_82)
last_train_price_82 = train_data_82['Price'].iloc[-1]

# Forecast cho test period
test_forecast_82 = fitted_garch_82.forecast(horizon=test_size_days_82)
forecast_returns_82 = test_forecast_82.mean.iloc[-1].values / 100

# Tính predicted prices cho test set 8:2
predicted_prices_test_82 = []
current_price_82 = last_train_price_82

for i in range(test_size_days_82):
    predicted_return = forecast_returns_82[i]
    current_price_82 = current_price_82 * np.exp(predicted_return)
    predicted_prices_test_82.append(current_price_82)

predicted_prices_test_82 = np.array(predicted_prices_test_82)

# Tính metrics cho 8:2
actual_test_prices_82 = test_data_82['Price'].values
mape_82 = mean_absolute_percentage_error(actual_test_prices_82, predicted_prices_test_82)
mse_82 = mean_squared_error(actual_test_prices_82, predicted_prices_test_82)
rmse_82 = np.sqrt(mse_82)

print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'AIC: {aic_82:.2f}')
print(f'BIC: {bic_82:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:
MAPE: 0.39%
MSE: 1602692.67
RMSE: 1265.97
AIC: 16298.99
BIC: 16322.59

Dự đoán tương lai 8:2¶

In [41]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho 8:2
forecast_30_82, vol_30_82 = forecast_garch_prices(fitted_garch_82, last_price, 30)
forecast_60_82, vol_60_82 = forecast_garch_prices(fitted_garch_82, last_price, 60)
forecast_90_82, vol_90_82 = forecast_garch_prices(fitted_garch_82, last_price, 90)

# Tạo DataFrames cho 8:2
forecast_df_30_82 = pd.DataFrame(forecast_30_82, index=forecast_dates_30, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecast_60_82, index=forecast_dates_60, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecast_90_82, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa 8:2
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán trên test set (8:2)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'], 
         label='Dự đoán 30 ngày (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'], 
         label='Dự đoán 60 ngày (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'], 
         label='Dự đoán 90 ngày (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Ethereum bằng GARCH(1,1) - Split 8:2', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

print(f'\nDự đoán giá Ethereum 30 ngày tiếp theo (GARCH 8:2):')
print(f'Giá cao nhất: ${forecast_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_82.min():.2f}')
print(f'Giá trung bình: ${forecast_30_82.mean():.2f}')
print(f'Volatility trung bình: {vol_30_82.mean():.2f}%')
No description has been provided for this image
Dự đoán giá Ethereum 30 ngày tiếp theo (GARCH 8:2):
Giá cao nhất: $3007.28
Giá thấp nhất: $1942.20
Giá trung bình: $2483.53
Volatility trung bình: 4.34%

Chia 9:1¶

Chuẩn bị dữ liệu 9:1¶

In [42]:
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
In [43]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data_clean) * 0.9)
train_data_91 = data_clean.iloc[0:train_size_91].copy()
test_data_91 = data_clean.iloc[train_size_91:].copy()

# Lấy returns cho train và test
train_returns_91 = train_data_91['Log_Return'].values
test_returns_91 = test_data_91['Log_Return'].values

print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3032
Kích thước tập test 9:1: 337

Huấn luyện mô hình GARCH 9:1¶

In [44]:
# Fit mô hình GARCH(1,1) cho split 9:1
print("Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...")
fitted_garch_91, garch_model_91 = build_garch_model(train_returns_91, p=1, q=1)

# Đánh giá mô hình
aic_91, bic_91 = evaluate_garch_model(fitted_garch_91)
Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -8982.92
Distribution:                  Normal   AIC:                           17973.8
Method:            Maximum Likelihood   BIC:                           17997.9
                                        No. Observations:                 3032
Date:                Tue, Jun 03 2025   Df Residuals:                     3031
Time:                        20:39:09   Df Model:                            1
                                Mean Model                                
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
mu             0.1493  7.617e-02      1.960  4.998e-02 [1.055e-05,  0.299]
                              Volatility Model                             
===========================================================================
                 coef    std err          t      P>|t|     95.0% Conf. Int.
---------------------------------------------------------------------------
omega          0.6975      0.379      1.839  6.588e-02 [-4.579e-02,  1.441]
alpha[1]       0.1120  3.552e-02      3.154  1.612e-03  [4.240e-02,  0.182]
beta[1]        0.8694  4.112e-02     21.145  3.051e-99    [  0.789,  0.950]
===========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0019
Ljung-Box test p-value (squared residuals): 0.0000

Đánh giá mô hình 9:1¶

In [45]:
# Dự đoán trên tập test 9:1
test_size_days_91 = len(test_data_91)
last_train_price_91 = train_data_91['Price'].iloc[-1]

# Forecast cho test period
test_forecast_91 = fitted_garch_91.forecast(horizon=test_size_days_91)
forecast_returns_91 = test_forecast_91.mean.iloc[-1].values / 100

# Tính predicted prices cho test set 9:1
predicted_prices_test_91 = []
current_price_91 = last_train_price_91

for i in range(test_size_days_91):
    predicted_return = forecast_returns_91[i]
    current_price_91 = current_price_91 * np.exp(predicted_return)
    predicted_prices_test_91.append(current_price_91)

predicted_prices_test_91 = np.array(predicted_prices_test_91)

# Tính metrics cho 9:1
actual_test_prices_91 = test_data_91['Price'].values
mape_91 = mean_absolute_percentage_error(actual_test_prices_91, predicted_prices_test_91)
mse_91 = mean_squared_error(actual_test_prices_91, predicted_prices_test_91)
rmse_91 = np.sqrt(mse_91)

print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'AIC: {aic_91:.2f}')
print(f'BIC: {bic_91:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:
MAPE: 0.74%
MSE: 3956901.43
RMSE: 1989.20
AIC: 17973.83
BIC: 17997.90

Dự đoán tương lai 9:1¶

In [46]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho 9:1
forecast_30_91, vol_30_91 = forecast_garch_prices(fitted_garch_91, last_price, 30)
forecast_60_91, vol_60_91 = forecast_garch_prices(fitted_garch_91, last_price, 60)
forecast_90_91, vol_90_91 = forecast_garch_prices(fitted_garch_91, last_price, 90)

# Tạo DataFrames cho 9:1
forecast_df_30_91 = pd.DataFrame(forecast_30_91, index=forecast_dates_30, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecast_60_91, index=forecast_dates_60, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecast_90_91, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa 9:1
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán trên test set (9:1)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'], 
         label='Dự đoán 30 ngày (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'], 
         label='Dự đoán 60 ngày (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'], 
         label='Dự đoán 90 ngày (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Ethereum bằng GARCH(1,1) - Split 9:1', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

print(f'\nDự đoán giá Ethereum 30 ngày tiếp theo (GARCH 9:1):')
print(f'Giá cao nhất: ${forecast_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_91.min():.2f}')
print(f'Giá trung bình: ${forecast_30_91.mean():.2f}')
print(f'Volatility trung bình: {vol_30_91.mean():.2f}%')
No description has been provided for this image
Dự đoán giá Ethereum 30 ngày tiếp theo (GARCH 9:1):
Giá cao nhất: $2961.26
Giá thấp nhất: $2020.33
Giá trung bình: $2500.42
Volatility trung bình: 3.86%

So sánh 3 tỉ lệ GARCH¶

In [47]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu cho GARCH
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL")
print("="*80)

# Thu thập thông tin từ 3 splits
garch_splits_info = {
    '7:3': {
        'train_size': len(train_data),
        'test_size': len(test_data),
        'mape': mape,
        'mse': mse,
        'rmse': rmse,
        'aic': aic,
        'bic': bic,
        'avg_volatility': vol_30.mean()
    },
    '8:2': {
        'train_size': len(train_data_82),
        'test_size': len(test_data_82),
        'mape': mape_82,
        'mse': mse_82,
        'rmse': rmse_82,
        'aic': aic_82,
        'bic': bic_82,
        'avg_volatility': vol_30_82.mean()
    },
    '9:1': {
        'train_size': len(train_data_91),
        'test_size': len(test_data_91),
        'mape': mape_91,
        'mse': mse_91,
        'rmse': rmse_91,
        'aic': aic_91,
        'bic': bic_91,
        'avg_volatility': vol_30_91.mean()
    }
}

# In bảng so sánh GARCH
for split, info in garch_splits_info.items():
    print(f"\n{split} Split (GARCH):")
    print(f"  Kích thước train: {info['train_size']:,} mẫu")
    print(f"  Kích thước test: {info['test_size']:,} mẫu")
    print(f"  MAPE: {info['mape']:.2f}%")
    print(f"  MSE: {info['mse']:,.2f}")
    print(f"  RMSE: {info['rmse']:,.2f}")
    print(f"  AIC: {info['aic']:.2f}")
    print(f"  BIC: {info['bic']:.2f}")
    print(f"  Avg Volatility: {info['avg_volatility']:.2f}%")
================================================================================
SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL
================================================================================

7:3 Split (GARCH):
  Kích thước train: 2,358 mẫu
  Kích thước test: 1,011 mẫu
  MAPE: 1.01%
  MSE: 9,081,335.20
  RMSE: 3,013.53
  AIC: 14545.22
  BIC: 14568.28
  Avg Volatility: 5.39%

8:2 Split (GARCH):
  Kích thước train: 2,695 mẫu
  Kích thước test: 674 mẫu
  MAPE: 0.39%
  MSE: 1,602,692.67
  RMSE: 1,265.97
  AIC: 16298.99
  BIC: 16322.59
  Avg Volatility: 4.34%

9:1 Split (GARCH):
  Kích thước train: 3,032 mẫu
  Kích thước test: 337 mẫu
  MAPE: 0.74%
  MSE: 3,956,901.43
  RMSE: 1,989.20
  AIC: 17973.83
  BIC: 17997.90
  Avg Volatility: 3.86%
In [48]:
# Vẽ biểu đồ so sánh các metrics cho GARCH
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']

# 1. So sánh MAPE
mape_values = [garch_splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%) - GARCH', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
    axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 2. So sánh RMSE
rmse_values = [garch_splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD) - GARCH', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
    axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')

# 3. So sánh AIC
aic_values = [garch_splits_info[split]['aic'] for split in splits]
axes[0, 2].bar(splits, aic_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh AIC - GARCH', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('AIC')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(aic_values):
    axes[0, 2].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')

# 4. So sánh BIC
bic_values = [garch_splits_info[split]['bic'] for split in splits]
axes[1, 0].bar(splits, bic_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh BIC - GARCH', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('BIC')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(bic_values):
    axes[1, 0].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')

# 5. So sánh Average Volatility
vol_values = [garch_splits_info[split]['avg_volatility'] for split in splits]
axes[1, 1].bar(splits, vol_values, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Avg Volatility (%) - GARCH', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('Volatility (%)')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(vol_values):
    axes[1, 1].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 6. So sánh kích thước test set
test_sizes = [garch_splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set - GARCH', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
    axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [49]:
# Tạo DataFrame tổng hợp kết quả GARCH
garch_comparison_df = pd.DataFrame({
    'Split': ['7:3', '8:2', '9:1'],
    'Train_Size': [garch_splits_info[split]['train_size'] for split in splits],
    'Test_Size': [garch_splits_info[split]['test_size'] for split in splits],
    'MAPE (%)': [garch_splits_info[split]['mape'] for split in splits],
    'RMSE (USD)': [garch_splits_info[split]['rmse'] for split in splits],
    'MSE': [garch_splits_info[split]['mse'] for split in splits],
    'AIC': [garch_splits_info[split]['aic'] for split in splits],
    'BIC': [garch_splits_info[split]['bic'] for split in splits],
    'Avg_Volatility (%)': [garch_splits_info[split]['avg_volatility'] for split in splits]
})

print("\nBẢNG TỔNG HỢP KẾT QUẢ GARCH:")
print("="*100)
print(garch_comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ GARCH:
====================================================================================================
Split  Train_Size  Test_Size  MAPE (%)  RMSE (USD)          MSE        AIC        BIC  Avg_Volatility (%)
  7:3        2358       1011    1.0094   3013.5254 9081335.1999 14545.2183 14568.2806              5.3929
  8:2        2695        674    0.3860   1265.9750 1602692.6673 16298.9918 16322.5884              4.3418
  9:1        3032        337    0.7437   1989.1962 3956901.4280 17973.8326 17997.9006              3.8590
In [50]:
# Phân tích và đưa ra khuyến nghị cho GARCH
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL")
print("="*80)

# Tìm split tốt nhất cho từng metric
best_mape_split_garch = splits[np.argmin([garch_splits_info[split]['mape'] for split in splits])]
best_rmse_split_garch = splits[np.argmin([garch_splits_info[split]['rmse'] for split in splits])]
best_aic_split_garch = splits[np.argmin([garch_splits_info[split]['aic'] for split in splits])]
best_bic_split_garch = splits[np.argmin([garch_splits_info[split]['bic'] for split in splits])]

print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):")
print(f"   • Tốt nhất theo MAPE: {best_mape_split_garch} ({garch_splits_info[best_mape_split_garch]['mape']:.2f}%)")
print(f"   • Tốt nhất theo RMSE: {best_rmse_split_garch} ({garch_splits_info[best_rmse_split_garch]['rmse']:,.2f} USD)")
print(f"   • Tốt nhất theo AIC: {best_aic_split_garch} ({garch_splits_info[best_aic_split_garch]['aic']:.2f})")
print(f"   • Tốt nhất theo BIC: {best_bic_split_garch} ({garch_splits_info[best_bic_split_garch]['bic']:.2f})")

# Tính điểm tổng hợp cho GARCH
def calculate_garch_rank_score(splits_info):
    scores = {}
    splits_list = list(splits_info.keys())
    
    # Rank cho các metrics (thấp hơn = tốt hơn)
    mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
    rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
    aic_rank = sorted(splits_list, key=lambda x: splits_info[x]['aic'])
    bic_rank = sorted(splits_list, key=lambda x: splits_info[x]['bic'])
    
    for split in splits_list:
        # Điểm rank với trọng số cho GARCH
        score = (mape_rank.index(split) + 1) * 0.35 + \
                (rmse_rank.index(split) + 1) * 0.35 + \
                (aic_rank.index(split) + 1) * 0.15 + \
                (bic_rank.index(split) + 1) * 0.15
        scores[split] = score
    
    return scores

# Tính điểm tổng hợp
garch_scores = calculate_garch_rank_score(garch_splits_info)
best_overall_split_garch = min(garch_scores, key=garch_scores.get)

print(f"\n2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):")
for split in garch_splits_info.keys():
    print(f"   • {split}: {garch_scores[split]:.2f} điểm")

print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:")
print(f"   🏆 MÔ HÌNH GARCH TỐT NHẤT: Split {best_overall_split_garch}")
print(f"   📊 Lý do:")
print(f"      - MAPE: {garch_splits_info[best_overall_split_garch]['mape']:.2f}%")
print(f"      - RMSE: {garch_splits_info[best_overall_split_garch]['rmse']:,.2f} USD")
print(f"      - AIC: {garch_splits_info[best_overall_split_garch]['aic']:.2f}")
print(f"      - BIC: {garch_splits_info[best_overall_split_garch]['bic']:.2f}")
print(f"      - Avg Volatility: {garch_splits_info[best_overall_split_garch]['avg_volatility']:.2f}%")
print(f"      - Tập test có {garch_splits_info[best_overall_split_garch]['test_size']:,} mẫu")

print(f"\n4. NHẬN XÉT VỀ GARCH MODEL:")
print("   • GARCH hiệu quả trong việc mô hình hóa volatility clustering")
print("   • Phù hợp với dữ liệu tài chính có tính heteroskedasticity")
print("   • Cung cấp forecast về volatility cùng với price prediction")
print("   • AIC và BIC giúp đánh giá model fit và complexity")

print(f"\n   ⚠️  LƯU Ý GARCH: Model này tập trung vào volatility modeling")
print(f"   và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như Ethereum.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL
================================================================================

1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):
   • Tốt nhất theo MAPE: 8:2 (0.39%)
   • Tốt nhất theo RMSE: 8:2 (1,265.97 USD)
   • Tốt nhất theo AIC: 7:3 (14545.22)
   • Tốt nhất theo BIC: 7:3 (14568.28)

2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):
   • 7:3: 2.40 điểm
   • 8:2: 1.30 điểm
   • 9:1: 2.30 điểm

3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:
   🏆 MÔ HÌNH GARCH TỐT NHẤT: Split 8:2
   📊 Lý do:
      - MAPE: 0.39%
      - RMSE: 1,265.97 USD
      - AIC: 16298.99
      - BIC: 16322.59
      - Avg Volatility: 4.34%
      - Tập test có 674 mẫu

4. NHẬN XÉT VỀ GARCH MODEL:
   • GARCH hiệu quả trong việc mô hình hóa volatility clustering
   • Phù hợp với dữ liệu tài chính có tính heteroskedasticity
   • Cung cấp forecast về volatility cùng với price prediction
   • AIC và BIC giúp đánh giá model fit và complexity

   ⚠️  LƯU Ý GARCH: Model này tập trung vào volatility modeling
   và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như Ethereum.
In [51]:
# Vẽ so sánh volatility patterns giữa các splits
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

# Plot conditional volatility cho từng split
fitted_models = [fitted_garch, fitted_garch_82, fitted_garch_91]
train_data_list = [train_data, train_data_82, train_data_91]
split_names = ['7:3', '8:2', '9:1']

for i, (model, train_data_split, split_name) in enumerate(zip(fitted_models, train_data_list, split_names)):
    axes[i].plot(train_data_split.index, train_data_split['Log_Return'] * 100, 
                 alpha=0.7, label='Returns (%)', color='lightblue')
    axes[i].plot(train_data_split.index, model.conditional_volatility, 
                 color='red', linewidth=2, label='Conditional Volatility')
    axes[i].set_title(f'Volatility Pattern - Split {split_name}', fontweight='bold')
    axes[i].set_ylabel('Returns (%) / Volatility')
    axes[i].legend()
    axes[i].grid(True, alpha=0.3)
    axes[i].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()
No description has been provided for this image
In [52]:
# Vẽ biểu đồ radar cho so sánh tổng thể GARCH
def create_garch_radar_chart():
    # Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
    metrics = ['MAPE', 'RMSE', 'AIC', 'BIC', 'Test_Size']
    
    # Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
    data = {}
    for split in splits:
        mape_norm = 1 - (garch_splits_info[split]['mape'] - min([garch_splits_info[s]['mape'] for s in splits])) / \
                   (max([garch_splits_info[s]['mape'] for s in splits]) - min([garch_splits_info[s]['mape'] for s in splits]))
        
        rmse_norm = 1 - (garch_splits_info[split]['rmse'] - min([garch_splits_info[s]['rmse'] for s in splits])) / \
                   (max([garch_splits_info[s]['rmse'] for s in splits]) - min([garch_splits_info[s]['rmse'] for s in splits]))
        
        aic_norm = 1 - (garch_splits_info[split]['aic'] - min([garch_splits_info[s]['aic'] for s in splits])) / \
                  (max([garch_splits_info[s]['aic'] for s in splits]) - min([garch_splits_info[s]['aic'] for s in splits]))
        
        bic_norm = 1 - (garch_splits_info[split]['bic'] - min([garch_splits_info[s]['bic'] for s in splits])) / \
                  (max([garch_splits_info[s]['bic'] for s in splits]) - min([garch_splits_info[s]['bic'] for s in splits]))
        
        test_size_norm = (garch_splits_info[split]['test_size'] - min([garch_splits_info[s]['test_size'] for s in splits])) / \
                        (max([garch_splits_info[s]['test_size'] for s in splits]) - min([garch_splits_info[s]['test_size'] for s in splits]))
        
        data[split] = [mape_norm, rmse_norm, aic_norm, bic_norm, test_size_norm]
    
    # Tạo radar chart
    angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
    angles += angles[:1]  # Complete the circle
    
    fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
    
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
    
    for i, (split, values) in enumerate(data.items()):
        values += values[:1]  # Complete the circle
        ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
        ax.fill(angles, values, alpha=0.25, color=colors[i])
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(metrics)
    ax.set_ylim(0, 1)
    ax.set_title('So sánh tổng thể các Split GARCH (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
    ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
    ax.grid(True)
    
    plt.tight_layout()
    plt.show()

create_garch_radar_chart()
No description has been provided for this image

Kết luận cuối cùng - GARCH Model¶

Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình GARCH với split tỉ lệ dữ liệu tốt nhất đã được xác định.

Các yếu tố được xem xét cho GARCH:

  • MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán giá
  • RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
  • AIC (Akaike Information Criterion): Đánh giá model fit và complexity
  • BIC (Bayesian Information Criterion): Đánh giá model với penalty cho complexity
  • Volatility Forecasting: Khả năng dự đoán volatility (điểm mạnh của GARCH)
  • Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá

Ưu điểm của GARCH:

  • Mô hình hóa hiệu quả volatility clustering trong dữ liệu tài chính
  • Cung cấp forecast cho cả price và volatility
  • Phù hợp với tính chất heteroskedasticity của Ethereum
  • Cho phép risk assessment thông qua volatility predictions

Khuyến nghị sử dụng: Mô hình GARCH với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá và volatility Ethereum, đặc biệt hiệu quả cho short-term forecasting và risk management.

XRP¶

Import thư viện¶

In [53]:
%pip install arch seaborn
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from arch import arch_model
from arch.univariate import GARCH, ConstantMean
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
import seaborn as sns
# Thêm import cho kiểm tra tính dừng
from statsmodels.tsa.stattools import adfuller
Requirement already satisfied: arch in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (7.2.0)
Requirement already satisfied: seaborn in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (0.13.2)
Requirement already satisfied: numpy>=1.22.3 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.1.3)
Requirement already satisfied: scipy>=1.8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (1.15.3)
Requirement already satisfied: pandas>=1.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.2.3)
Requirement already satisfied: statsmodels>=0.12 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (0.14.4)
Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from seaborn) (3.10.3)
Requirement already satisfied: contourpy>=1.0.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.2)
Requirement already satisfied: cycler>=0.10 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.58.1)
Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.8)
Requirement already satisfied: packaging>=20.0 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0)
Requirement already satisfied: pillow>=8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.2.1)
Requirement already satisfied: pyparsing>=2.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2)
Requirement already satisfied: patsy>=0.5.6 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from statsmodels>=0.12->arch) (1.0.1)
Requirement already satisfied: six>=1.5 in c:\users\hii\appdata\roaming\python\python311\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0)
Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 24.0 -> 25.1.1
[notice] To update, run: python.exe -m pip install --upgrade pip

XRP Dataset¶

Import csv¶

In [54]:
# Đọc file XRP
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\XRP Historical Data.csv"

data = pd.read_csv(file_path)

# Loại bỏ dấu phẩy và chuyển đổi thành float cho các cột Price và Open
for col in ['Price', 'Open']:
    data[col] = data[col].astype(str).str.replace(',', '', regex=False).astype(float)

# Hàm xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
    val = str(val).replace(',', '').strip()
    if 'K' in val:
        return float(val.replace('K', '')) * 1_000
    elif 'M' in val:
        return float(val.replace('M', '')) * 1_000_000
    elif 'B' in val:
        return float(val.replace('B', '')) * 1_000_000_000
    else:
        try:
            return float(val)
        except ValueError:
            return np.nan  # Nếu chuỗi rỗng hoặc không hợp lệ

# Áp dụng xử lý cho cột 'Vol.'
data['Vol.'] = data['Vol.'].apply(convert_volume)

# Kiểm tra NaN trước xử lý
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Nội suy và điền 0 nếu còn thiếu
data['Vol.'] = data['Vol.'].interpolate(method='linear')
data['Vol.'] = data['Vol.'].fillna(0)

# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")

# Chuyển cột Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)

# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()

print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 12
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3369, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']

First 5 rows:
             Price    Open     Vol.
Date                               
2016-03-10  0.0082  0.0081  59130.0
2016-03-11  0.0092  0.0082  25820.0
2016-03-12  0.0081  0.0092  78230.0
2016-03-13  0.0082  0.0081    620.0
2016-03-14  0.0083  0.0082  19310.0
Tổng số dữ liệu: 3369 dòng

Chia 7:3¶

Chuẩn bị dữ liệu cho GARCH¶

In [55]:
# Tính log returns cho GARCH model
data['Log_Return'] = np.log(data['Price'] / data['Price'].shift(1))
data['Simple_Return'] = data['Price'].pct_change()

# Loại bỏ giá trị NaN
data_clean = data.dropna()

print(f"Dữ liệu sau khi tính returns: {len(data_clean)} dòng")
print("\nThống kê mô tả của Log Returns:")
print(data_clean['Log_Return'].describe())

# Kiểm tra tính dừng của chuỗi returns
from statsmodels.tsa.stattools import adfuller

def check_stationarity(series, name):
    result = adfuller(series.dropna())
    print(f'\n{name} - Kiểm định ADF:')
    print(f'ADF Statistic: {result[0]:.6f}')
    print(f'p-value: {result[1]:.6f}')
    print(f'Critical Values:')
    for key, value in result[4].items():
        print(f'\t{key}: {value:.3f}')
    
    if result[1] <= 0.05:
        print("✓ Chuỗi dừng (stationary)")
    else:
        print("✗ Chuỗi không dừng (non-stationary)")
    return result[1] <= 0.05

# Kiểm tra tính dừng
is_stationary = check_stationarity(data_clean['Log_Return'], 'Log Returns')
Dữ liệu sau khi tính returns: 3368 dòng

Thống kê mô tả của Log Returns:
count    3368.000000
mean        0.001652
std         0.064184
min        -0.653301
25%        -0.021956
50%         0.000000
75%         0.020798
max         1.027995
Name: Log_Return, dtype: float64

Log Returns - Kiểm định ADF:
ADF Statistic: -12.279138
p-value: 0.000000
Critical Values:
	1%: -3.432
	5%: -2.862
	10%: -2.567
✓ Chuỗi dừng (stationary)
In [56]:
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data_clean) * 0.7)
train_data = data_clean.iloc[0:train_size].copy()
test_data = data_clean.iloc[train_size:].copy()

# Lấy returns cho train và test
train_returns = train_data['Log_Return'].values
test_returns = test_data['Log_Return'].values

print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
print(f"Train returns shape: {train_returns.shape}")
print(f"Test returns shape: {test_returns.shape}")
Kích thước tập train: 2357
Kích thước tập test: 1011
Train returns shape: (2357,)
Test returns shape: (1011,)

Xây dựng mô hình GARCH¶

In [57]:
def build_garch_model(returns_data, p=1, q=1):
    """
    Xây dựng mô hình GARCH(p,q)
    
    Args:
        returns_data: Chuỗi returns
        p: Order của GARCH term
        q: Order của ARCH term
    
    Returns:
        Fitted GARCH model
    """
    # Nhân returns với 100 để có scale phù hợp
    returns_scaled = returns_data * 100
    
    # Tạo mô hình GARCH
    model = arch_model(returns_scaled, vol='Garch', p=p, q=q, dist='normal')
    
    # Fit mô hình
    fitted_model = model.fit(disp='off')
    
    return fitted_model, model

def evaluate_garch_model(fitted_model):
    """
    Đánh giá mô hình GARCH
    """
    print("="*50)
    print("THÔNG TIN MÔ HÌNH GARCH")
    print("="*50)
    print(fitted_model.summary())
    
    # Kiểm tra phần dư
    residuals = fitted_model.resid
    standardized_residuals = residuals / fitted_model.conditional_volatility
    
    # Ljung-Box test cho phần dư
    lb_result = acorr_ljungbox(residuals, lags=10, return_df=True)
    print(f"\nLjung-Box test p-value (residuals): {lb_result['lb_pvalue'].iloc[-1]:.4f}")
    
    # Ljung-Box test cho phần dư bình phương
    lb_result_sq = acorr_ljungbox(residuals**2, lags=10, return_df=True)
    print(f"Ljung-Box test p-value (squared residuals): {lb_result_sq['lb_pvalue'].iloc[-1]:.4f}")
    
    return fitted_model.aic, fitted_model.bic
In [58]:
def forecast_garch_prices(fitted_model, initial_price, forecast_horizon):
    """
    Dự đoán giá sử dụng GARCH model
    
    Args:
        fitted_model: Mô hình GARCH đã fit
        initial_price: Giá ban đầu
        forecast_horizon: Số ngày dự đoán
    
    Returns:
        Dự đoán giá và volatility
    """
    # Forecast returns và volatility
    forecast = fitted_model.forecast(horizon=forecast_horizon)
    
    # Lấy mean và variance dự đoán
    forecast_mean = forecast.mean.iloc[-1].values / 100  # Scale về decimal
    forecast_variance = forecast.variance.iloc[-1].values / 10000  # Scale về decimal
    
    # Tạo random walks cho price prediction
    np.random.seed(42)  # Để kết quả reproducible
    
    predicted_prices = []
    current_price = initial_price
    
    for i in range(forecast_horizon):
        # Sử dụng mean return và add random noise based on predicted volatility
        return_pred = forecast_mean[i] + np.random.normal(0, np.sqrt(forecast_variance[i]))
        current_price = current_price * np.exp(return_pred)
        predicted_prices.append(current_price)
    
    return np.array(predicted_prices), np.sqrt(forecast_variance) * 100
In [59]:
def plot_garch_diagnostics(fitted_model, returns_data, train_data_index):
    """
    Vẽ biểu đồ chẩn đoán cho mô hình GARCH
    """
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    
    # 1. Returns và Conditional Volatility
    # Sử dụng index từ train_data thay vì fitted_model.resid.index
    dates = train_data_index
    axes[0, 0].plot(dates, returns_data * 100, alpha=0.7, label='Returns (%)')
    axes[0, 0].plot(dates, fitted_model.conditional_volatility, color='red', label='Conditional Volatility')
    axes[0, 0].set_title('Returns và Conditional Volatility')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    axes[0, 0].tick_params(axis='x', rotation=45)
    
    # 2. Standardized Residuals
    std_resid = fitted_model.resid / fitted_model.conditional_volatility
    axes[0, 1].plot(dates, std_resid)
    axes[0, 1].set_title('Standardized Residuals')
    axes[0, 1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
    axes[0, 1].grid(True, alpha=0.3)
    axes[0, 1].tick_params(axis='x', rotation=45)
    
    # 3. Q-Q Plot
    stats.probplot(std_resid, dist="norm", plot=axes[1, 0])
    axes[1, 0].set_title('Q-Q Plot of Standardized Residuals')
    axes[1, 0].grid(True, alpha=0.3)
    
    # 4. ACF of Squared Residuals
    from statsmodels.tsa.stattools import acf
    squared_resid = std_resid ** 2
    lags = 20
    acf_vals = acf(squared_resid, nlags=lags)
    axes[1, 1].bar(range(lags+1), acf_vals, alpha=0.7)
    axes[1, 1].set_title('ACF of Squared Standardized Residuals')
    axes[1, 1].set_xlabel('Lag')
    axes[1, 1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

Huấn luyện mô hình GARCH¶

In [60]:
# Fit mô hình GARCH(1,1) cho tập train
print("Đang huấn luyện mô hình GARCH(1,1)...")
fitted_garch, garch_model = build_garch_model(train_returns, p=1, q=1)

# Đánh giá mô hình
aic, bic = evaluate_garch_model(fitted_garch)

# Vẽ biểu đồ chẩn đoán với train_returns và train_data index
plot_garch_diagnostics(fitted_garch, train_returns, train_data.index)
Đang huấn luyện mô hình GARCH(1,1)...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -7432.28
Distribution:                  Normal   AIC:                           14872.6
Method:            Maximum Likelihood   BIC:                           14895.6
                                        No. Observations:                 2357
Date:                Tue, Jun 03 2025   Df Residuals:                     2356
Time:                        20:39:16   Df Model:                            1
                                 Mean Model                                
===========================================================================
                 coef    std err          t      P>|t|     95.0% Conf. Int.
---------------------------------------------------------------------------
mu            -0.2764      0.121     -2.286  2.228e-02 [ -0.513,-3.938e-02]
                            Volatility Model                            
========================================================================
                 coef    std err          t      P>|t|  95.0% Conf. Int.
------------------------------------------------------------------------
omega          4.6448      1.580      2.939  3.288e-03 [  1.548,  7.742]
alpha[1]       0.4258      0.140      3.034  2.411e-03 [  0.151,  0.701]
beta[1]        0.5742  9.992e-02      5.747  9.101e-09 [  0.378,  0.770]
========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0000
Ljung-Box test p-value (squared residuals): 0.0000
No description has been provided for this image

Đánh giá mô hình trên tập test¶

In [61]:
# Dự đoán trên tập test
test_size_days = len(test_data)
last_train_price = train_data['Price'].iloc[-1]

# Forecast cho test period
test_forecast = fitted_garch.forecast(horizon=test_size_days)
forecast_returns = test_forecast.mean.iloc[-1].values / 100
forecast_volatility = np.sqrt(test_forecast.variance.iloc[-1].values) / 100

# Tính predicted prices cho test set
predicted_prices_test = []
current_price = last_train_price

for i in range(test_size_days):
    # Sử dụng predicted return
    predicted_return = forecast_returns[i]
    current_price = current_price * np.exp(predicted_return)
    predicted_prices_test.append(current_price)

predicted_prices_test = np.array(predicted_prices_test)

# Tính metrics
actual_test_prices = test_data['Price'].values
mape = mean_absolute_percentage_error(actual_test_prices, predicted_prices_test)
mse = mean_squared_error(actual_test_prices, predicted_prices_test)
rmse = np.sqrt(mse)

print(f'Kết quả đánh giá mô hình GARCH(1,1):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'AIC: {aic:.2f}')
print(f'BIC: {bic:.2f}')

# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data.index, actual_test_prices, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (7:3)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1):
MAPE: 0.75%
MSE: 1.19
RMSE: 1.09
AIC: 14872.56
BIC: 14895.62
No description has been provided for this image

Dự đoán tương lai¶

In [62]:
# Dự đoán 30, 60, 90 ngày tiếp theo
last_price = data_clean['Price'].iloc[-1]

# Forecast prices
forecast_30, vol_30 = forecast_garch_prices(fitted_garch, last_price, 30)
forecast_60, vol_60 = forecast_garch_prices(fitted_garch, last_price, 60)
forecast_90, vol_90 = forecast_garch_prices(fitted_garch, last_price, 90)

# Tạo dates cho forecasts
forecast_dates_30 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')

# Tạo DataFrames
forecast_df_30 = pd.DataFrame(forecast_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecast_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecast_90, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa
plt.figure(figsize=(16, 10))

# Vẽ dữ liệu lịch sử
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)

# Vẽ dự đoán test
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán trên test set', color='orange', linewidth=2, alpha=0.8)

# Vẽ dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'], 
         label='Dự đoán 30 ngày', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'], 
         label='Dự đoán 60 ngày', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'], 
         label='Dự đoán 90 ngày', color='purple', linestyle='--', linewidth=2, alpha=0.3)

# Đường phân cách
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')

plt.title('Dự đoán giá XRP bằng GARCH(1,1) - Split 7:3', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
No description has been provided for this image
In [63]:
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (GARCH 7:3):')
print(f'Giá cao nhất: ${forecast_30.max():.4f}')
print(f'Giá thấp nhất: ${forecast_30.min():.4f}')
print(f'Giá trung bình: ${forecast_30.mean():.4f}')
print(f'Volatility trung bình: {vol_30.mean():.2f}%')
Dự đoán giá XRP 30 ngày tiếp theo (GARCH 7:3):
Giá cao nhất: $2.7270
Giá thấp nhất: $0.9456
Giá trung bình: $1.8551
Volatility trung bình: 8.88%

Chia 8:2¶

Chuẩn bị dữ liệu 8:2¶

In [64]:
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3368 dòng
In [65]:
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data_clean) * 0.8)
train_data_82 = data_clean.iloc[0:train_size_82].copy()
test_data_82 = data_clean.iloc[train_size_82:].copy()

# Lấy returns cho train và test
train_returns_82 = train_data_82['Log_Return'].values
test_returns_82 = test_data_82['Log_Return'].values

print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2694
Kích thước tập test 8:2: 674

Huấn luyện mô hình GARCH 8:2¶

In [66]:
# Fit mô hình GARCH(1,1) cho split 8:2
print("Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...")
fitted_garch_82, garch_model_82 = build_garch_model(train_returns_82, p=1, q=1)

# Đánh giá mô hình
aic_82, bic_82 = evaluate_garch_model(fitted_garch_82)
Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -8465.86
Distribution:                  Normal   AIC:                           16939.7
Method:            Maximum Likelihood   BIC:                           16963.3
                                        No. Observations:                 2694
Date:                Tue, Jun 03 2025   Df Residuals:                     2693
Time:                        20:39:18   Df Model:                            1
                                Mean Model                                
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
mu            -0.1646      0.111     -1.477      0.140 [ -0.383,5.385e-02]
                            Volatility Model                            
========================================================================
                 coef    std err          t      P>|t|  95.0% Conf. Int.
------------------------------------------------------------------------
omega          5.8760      2.221      2.645  8.163e-03 [  1.522, 10.230]
alpha[1]       0.3580      0.124      2.893  3.819e-03 [  0.115,  0.601]
beta[1]        0.5744  9.367e-02      6.132  8.663e-10 [  0.391,  0.758]
========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0000
Ljung-Box test p-value (squared residuals): 0.0000

Đánh giá mô hình 8:2¶

In [67]:
# Dự đoán trên tập test 8:2
test_size_days_82 = len(test_data_82)
last_train_price_82 = train_data_82['Price'].iloc[-1]

# Forecast cho test period
test_forecast_82 = fitted_garch_82.forecast(horizon=test_size_days_82)
forecast_returns_82 = test_forecast_82.mean.iloc[-1].values / 100

# Tính predicted prices cho test set 8:2
predicted_prices_test_82 = []
current_price_82 = last_train_price_82

for i in range(test_size_days_82):
    predicted_return = forecast_returns_82[i]
    current_price_82 = current_price_82 * np.exp(predicted_return)
    predicted_prices_test_82.append(current_price_82)

predicted_prices_test_82 = np.array(predicted_prices_test_82)

# Tính metrics cho 8:2
actual_test_prices_82 = test_data_82['Price'].values
mape_82 = mean_absolute_percentage_error(actual_test_prices_82, predicted_prices_test_82)
mse_82 = mean_squared_error(actual_test_prices_82, predicted_prices_test_82)
rmse_82 = np.sqrt(mse_82)

print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'AIC: {aic_82:.2f}')
print(f'BIC: {bic_82:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:
MAPE: 0.40%
MSE: 1.28
RMSE: 1.13
AIC: 16939.71
BIC: 16963.31
In [68]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho 8:2
forecast_30_82, vol_30_82 = forecast_garch_prices(fitted_garch_82, last_price, 30)
forecast_60_82, vol_60_82 = forecast_garch_prices(fitted_garch_82, last_price, 60)
forecast_90_82, vol_90_82 = forecast_garch_prices(fitted_garch_82, last_price, 90)

# Tạo DataFrames cho 8:2
forecast_df_30_82 = pd.DataFrame(forecast_30_82, index=forecast_dates_30, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecast_60_82, index=forecast_dates_60, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecast_90_82, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa 8:2
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán trên test set (8:2)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'], 
         label='Dự đoán 30 ngày (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'], 
         label='Dự đoán 60 ngày (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'], 
         label='Dự đoán 90 ngày (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá XRP bằng GARCH(1,1) - Split 8:2', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

print(f'\nDự đoán giá XRP 30 ngày tiếp theo (GARCH 8:2):')
print(f'Giá cao nhất: ${forecast_30_82.max():.4f}')
print(f'Giá thấp nhất: ${forecast_30_82.min():.4f}')
print(f'Giá trung bình: ${forecast_30_82.mean():.4f}')
print(f'Volatility trung bình: {vol_30_82.mean():.2f}%')
No description has been provided for this image
Dự đoán giá XRP 30 ngày tiếp theo (GARCH 8:2):
Giá cao nhất: $2.7553
Giá thấp nhất: $1.1705
Giá trung bình: $1.9621
Volatility trung bình: 7.51%

Chia 9:1¶

Chuẩn bị dữ liệu 9:1¶

In [69]:
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3368 dòng
In [70]:
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data_clean) * 0.9)
train_data_91 = data_clean.iloc[0:train_size_91].copy()
test_data_91 = data_clean.iloc[train_size_91:].copy()

# Lấy returns cho train và test
train_returns_91 = train_data_91['Log_Return'].values
test_returns_91 = test_data_91['Log_Return'].values

print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3031
Kích thước tập test 9:1: 337

Huấn luyện mô hình GARCH 9:1¶

In [71]:
# Fit mô hình GARCH(1,1) cho split 9:1
print("Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...")
fitted_garch_91, garch_model_91 = build_garch_model(train_returns_91, p=1, q=1)

# Đánh giá mô hình
aic_91, bic_91 = evaluate_garch_model(fitted_garch_91)
Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
                     Constant Mean - GARCH Model Results                      
==============================================================================
Dep. Variable:                      y   R-squared:                       0.000
Mean Model:             Constant Mean   Adj. R-squared:                  0.000
Vol Model:                      GARCH   Log-Likelihood:               -9339.78
Distribution:                  Normal   AIC:                           18687.6
Method:            Maximum Likelihood   BIC:                           18711.6
                                        No. Observations:                 3031
Date:                Tue, Jun 03 2025   Df Residuals:                     3030
Time:                        20:39:19   Df Model:                            1
                                Mean Model                                
==========================================================================
                 coef    std err          t      P>|t|    95.0% Conf. Int.
--------------------------------------------------------------------------
mu            -0.1458  9.665e-02     -1.509      0.131 [ -0.335,4.361e-02]
                            Volatility Model                            
========================================================================
                 coef    std err          t      P>|t|  95.0% Conf. Int.
------------------------------------------------------------------------
omega          4.8806      1.920      2.541  1.104e-02 [  1.117,  8.645]
alpha[1]       0.3566      0.120      2.975  2.931e-03 [  0.122,  0.591]
beta[1]        0.5869  9.155e-02      6.411  1.450e-10 [  0.407,  0.766]
========================================================================

Covariance estimator: robust

Ljung-Box test p-value (residuals): 0.0000
Ljung-Box test p-value (squared residuals): 0.0000

Đánh giá mô hình 9:1¶

In [72]:
# Dự đoán trên tập test 9:1
test_size_days_91 = len(test_data_91)
last_train_price_91 = train_data_91['Price'].iloc[-1]

# Forecast cho test period
test_forecast_91 = fitted_garch_91.forecast(horizon=test_size_days_91)
forecast_returns_91 = test_forecast_91.mean.iloc[-1].values / 100

# Tính predicted prices cho test set 9:1
predicted_prices_test_91 = []
current_price_91 = last_train_price_91

for i in range(test_size_days_91):
    predicted_return = forecast_returns_91[i]
    current_price_91 = current_price_91 * np.exp(predicted_return)
    predicted_prices_test_91.append(current_price_91)

predicted_prices_test_91 = np.array(predicted_prices_test_91)

# Tính metrics cho 9:1
actual_test_prices_91 = test_data_91['Price'].values
mape_91 = mean_absolute_percentage_error(actual_test_prices_91, predicted_prices_test_91)
mse_91 = mean_squared_error(actual_test_prices_91, predicted_prices_test_91)
rmse_91 = np.sqrt(mse_91)

print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'AIC: {aic_91:.2f}')
print(f'BIC: {bic_91:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:
MAPE: 0.59%
MSE: 2.42
RMSE: 1.56
AIC: 18687.55
BIC: 18711.62
In [73]:
# Dự đoán 30, 60, 90 ngày tiếp theo cho 9:1
forecast_30_91, vol_30_91 = forecast_garch_prices(fitted_garch_91, last_price, 30)
forecast_60_91, vol_60_91 = forecast_garch_prices(fitted_garch_91, last_price, 60)
forecast_90_91, vol_90_91 = forecast_garch_prices(fitted_garch_91, last_price, 90)

# Tạo DataFrames cho 9:1
forecast_df_30_91 = pd.DataFrame(forecast_30_91, index=forecast_dates_30, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecast_60_91, index=forecast_dates_60, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecast_90_91, index=forecast_dates_90, columns=['Price'])

# Trực quan hóa 9:1
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán trên test set (9:1)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'], 
         label='Dự đoán 30 ngày (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'], 
         label='Dự đoán 60 ngày (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'], 
         label='Dự đoán 90 ngày (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7, 
            label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá XRP bằng GARCH(1,1) - Split 9:1', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

print(f'\nDự đoán giá XRP 30 ngày tiếp theo (GARCH 9:1):')
print(f'Giá cao nhất: ${forecast_30_91.max():.4f}')
print(f'Giá thấp nhất: ${forecast_30_91.min():.4f}')
print(f'Giá trung bình: ${forecast_30_91.mean():.4f}')
print(f'Volatility trung bình: {vol_30_91.mean():.2f}%')
No description has been provided for this image
Dự đoán giá XRP 30 ngày tiếp theo (GARCH 9:1):
Giá cao nhất: $2.6703
Giá thấp nhất: $1.1947
Giá trung bình: $1.9445
Volatility trung bình: 6.97%

So sánh 3 tỉ lệ GARCH¶

In [74]:
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu cho GARCH
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL")
print("="*80)

# Thu thập thông tin từ 3 splits
garch_splits_info = {
    '7:3': {
        'train_size': len(train_data),
        'test_size': len(test_data),
        'mape': mape,
        'mse': mse,
        'rmse': rmse,
        'aic': aic,
        'bic': bic,
        'avg_volatility': vol_30.mean()
    },
    '8:2': {
        'train_size': len(train_data_82),
        'test_size': len(test_data_82),
        'mape': mape_82,
        'mse': mse_82,
        'rmse': rmse_82,
        'aic': aic_82,
        'bic': bic_82,
        'avg_volatility': vol_30_82.mean()
    },
    '9:1': {
        'train_size': len(train_data_91),
        'test_size': len(test_data_91),
        'mape': mape_91,
        'mse': mse_91,
        'rmse': rmse_91,
        'aic': aic_91,
        'bic': bic_91,
        'avg_volatility': vol_30_91.mean()
    }
}

# In bảng so sánh GARCH
for split, info in garch_splits_info.items():
    print(f"\n{split} Split (GARCH):")
    print(f"  Kích thước train: {info['train_size']:,} mẫu")
    print(f"  Kích thước test: {info['test_size']:,} mẫu")
    print(f"  MAPE: {info['mape']:.2f}%")
    print(f"  MSE: {info['mse']:,.2f}")
    print(f"  RMSE: {info['rmse']:,.2f}")
    print(f"  AIC: {info['aic']:.2f}")
    print(f"  BIC: {info['bic']:.2f}")
    print(f"  Avg Volatility: {info['avg_volatility']:.2f}%")
================================================================================
SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL
================================================================================

7:3 Split (GARCH):
  Kích thước train: 2,357 mẫu
  Kích thước test: 1,011 mẫu
  MAPE: 0.75%
  MSE: 1.19
  RMSE: 1.09
  AIC: 14872.56
  BIC: 14895.62
  Avg Volatility: 8.88%

8:2 Split (GARCH):
  Kích thước train: 2,694 mẫu
  Kích thước test: 674 mẫu
  MAPE: 0.40%
  MSE: 1.28
  RMSE: 1.13
  AIC: 16939.71
  BIC: 16963.31
  Avg Volatility: 7.51%

9:1 Split (GARCH):
  Kích thước train: 3,031 mẫu
  Kích thước test: 337 mẫu
  MAPE: 0.59%
  MSE: 2.42
  RMSE: 1.56
  AIC: 18687.55
  BIC: 18711.62
  Avg Volatility: 6.97%
In [75]:
# Vẽ biểu đồ so sánh các metrics cho GARCH
fig, axes = plt.subplots(2, 3, figsize=(18, 12))

splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']

# 1. So sánh MAPE
mape_values = [garch_splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%) - GARCH', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
    axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 2. So sánh RMSE
rmse_values = [garch_splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD) - GARCH', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
    axes[0, 1].text(i, v + 0.001, f'{v:.3f}', ha='center', va='bottom', fontweight='bold')

# 3. So sánh AIC
aic_values = [garch_splits_info[split]['aic'] for split in splits]
axes[0, 2].bar(splits, aic_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh AIC - GARCH', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('AIC')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(aic_values):
    axes[0, 2].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')

# 4. So sánh BIC
bic_values = [garch_splits_info[split]['bic'] for split in splits]
axes[1, 0].bar(splits, bic_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh BIC - GARCH', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('BIC')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(bic_values):
    axes[1, 0].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')

# 5. So sánh Average Volatility
vol_values = [garch_splits_info[split]['avg_volatility'] for split in splits]
axes[1, 1].bar(splits, vol_values, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Avg Volatility (%) - GARCH', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('Volatility (%)')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(vol_values):
    axes[1, 1].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')

# 6. So sánh kích thước test set
test_sizes = [garch_splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set - GARCH', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
    axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')

plt.tight_layout()
plt.show()
No description has been provided for this image
In [76]:
# Tạo DataFrame tổng hợp kết quả GARCH
garch_comparison_df = pd.DataFrame({
    'Split': ['7:3', '8:2', '9:1'],
    'Train_Size': [garch_splits_info[split]['train_size'] for split in splits],
    'Test_Size': [garch_splits_info[split]['test_size'] for split in splits],
    'MAPE (%)': [garch_splits_info[split]['mape'] for split in splits],
    'RMSE (USD)': [garch_splits_info[split]['rmse'] for split in splits],
    'MSE': [garch_splits_info[split]['mse'] for split in splits],
    'AIC': [garch_splits_info[split]['aic'] for split in splits],
    'BIC': [garch_splits_info[split]['bic'] for split in splits],
    'Avg_Volatility (%)': [garch_splits_info[split]['avg_volatility'] for split in splits]
})

print("\nBẢNG TỔNG HỢP KẾT QUẢ GARCH:")
print("="*100)
print(garch_comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ GARCH:
====================================================================================================
Split  Train_Size  Test_Size  MAPE (%)  RMSE (USD)    MSE        AIC        BIC  Avg_Volatility (%)
  7:3        2357       1011    0.7534      1.0917 1.1919 14872.5642 14895.6248              8.8841
  8:2        2694        674    0.3987      1.1327 1.2829 16939.7142 16963.3093              7.5125
  9:1        3031        337    0.5941      1.5554 2.4191 18687.5537 18711.6203              6.9669
In [77]:
# Phân tích và đưa ra khuyến nghị cho GARCH
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL")
print("="*80)

# Tìm split tốt nhất cho từng metric
best_mape_split_garch = splits[np.argmin([garch_splits_info[split]['mape'] for split in splits])]
best_rmse_split_garch = splits[np.argmin([garch_splits_info[split]['rmse'] for split in splits])]
best_aic_split_garch = splits[np.argmin([garch_splits_info[split]['aic'] for split in splits])]
best_bic_split_garch = splits[np.argmin([garch_splits_info[split]['bic'] for split in splits])]

print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):")
print(f"   • Tốt nhất theo MAPE: {best_mape_split_garch} ({garch_splits_info[best_mape_split_garch]['mape']:.2f}%)")
print(f"   • Tốt nhất theo RMSE: {best_rmse_split_garch} ({garch_splits_info[best_rmse_split_garch]['rmse']:.3f} USD)")
print(f"   • Tốt nhất theo AIC: {best_aic_split_garch} ({garch_splits_info[best_aic_split_garch]['aic']:.2f})")
print(f"   • Tốt nhất theo BIC: {best_bic_split_garch} ({garch_splits_info[best_bic_split_garch]['bic']:.2f})")

# Tính điểm tổng hợp cho GARCH
def calculate_garch_rank_score(splits_info):
    scores = {}
    splits_list = list(splits_info.keys())
    
    # Rank cho các metrics (thấp hơn = tốt hơn)
    mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
    rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
    aic_rank = sorted(splits_list, key=lambda x: splits_info[x]['aic'])
    bic_rank = sorted(splits_list, key=lambda x: splits_info[x]['bic'])
    
    for split in splits_list:
        # Điểm rank với trọng số cho GARCH
        score = (mape_rank.index(split) + 1) * 0.35 + \
                (rmse_rank.index(split) + 1) * 0.35 + \
                (aic_rank.index(split) + 1) * 0.15 + \
                (bic_rank.index(split) + 1) * 0.15
        scores[split] = score
    
    return scores

# Tính điểm tổng hợp
garch_scores = calculate_garch_rank_score(garch_splits_info)
best_overall_split_garch = min(garch_scores, key=garch_scores.get)

print(f"\n2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):")
for split in garch_splits_info.keys():
    print(f"   • {split}: {garch_scores[split]:.2f} điểm")

print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:")
print(f"   🏆 MÔ HÌNH GARCH TỐT NHẤT: Split {best_overall_split_garch}")
print(f"   📊 Lý do:")
print(f"      - MAPE: {garch_splits_info[best_overall_split_garch]['mape']:.2f}%")
print(f"      - RMSE: {garch_splits_info[best_overall_split_garch]['rmse']:.3f} USD")
print(f"      - AIC: {garch_splits_info[best_overall_split_garch]['aic']:.2f}")
print(f"      - BIC: {garch_splits_info[best_overall_split_garch]['bic']:.2f}")
print(f"      - Avg Volatility: {garch_splits_info[best_overall_split_garch]['avg_volatility']:.2f}%")
print(f"      - Tập test có {garch_splits_info[best_overall_split_garch]['test_size']:,} mẫu")

print(f"\n4. NHẬN XÉT VỀ GARCH MODEL:")
print("   • GARCH hiệu quả trong việc mô hình hóa volatility clustering")
print("   • Phù hợp với dữ liệu tài chính có tính heteroskedasticity")
print("   • Cung cấp forecast về volatility cùng với price prediction")
print("   • AIC và BIC giúp đánh giá model fit và complexity")

print(f"\n   ⚠️  LƯU Ý GARCH: Model này tập trung vào volatility modeling")
print(f"   và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như XRP.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL
================================================================================

1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):
   • Tốt nhất theo MAPE: 8:2 (0.40%)
   • Tốt nhất theo RMSE: 7:3 (1.092 USD)
   • Tốt nhất theo AIC: 7:3 (14872.56)
   • Tốt nhất theo BIC: 7:3 (14895.62)

2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):
   • 7:3: 1.70 điểm
   • 8:2: 1.65 điểm
   • 9:1: 2.65 điểm

3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:
   🏆 MÔ HÌNH GARCH TỐT NHẤT: Split 8:2
   📊 Lý do:
      - MAPE: 0.40%
      - RMSE: 1.133 USD
      - AIC: 16939.71
      - BIC: 16963.31
      - Avg Volatility: 7.51%
      - Tập test có 674 mẫu

4. NHẬN XÉT VỀ GARCH MODEL:
   • GARCH hiệu quả trong việc mô hình hóa volatility clustering
   • Phù hợp với dữ liệu tài chính có tính heteroskedasticity
   • Cung cấp forecast về volatility cùng với price prediction
   • AIC và BIC giúp đánh giá model fit và complexity

   ⚠️  LƯU Ý GARCH: Model này tập trung vào volatility modeling
   và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như XRP.
In [78]:
# Vẽ so sánh volatility patterns giữa các splits
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

# Plot conditional volatility cho từng split
fitted_models = [fitted_garch, fitted_garch_82, fitted_garch_91]
train_data_list = [train_data, train_data_82, train_data_91]
split_names = ['7:3', '8:2', '9:1']

for i, (model, train_data_split, split_name) in enumerate(zip(fitted_models, train_data_list, split_names)):
    axes[i].plot(train_data_split.index, train_data_split['Log_Return'] * 100, 
                 alpha=0.7, label='Returns (%)', color='lightblue')
    axes[i].plot(train_data_split.index, model.conditional_volatility, 
                 color='red', linewidth=2, label='Conditional Volatility')
    axes[i].set_title(f'Volatility Pattern - Split {split_name}', fontweight='bold')
    axes[i].set_ylabel('Returns (%) / Volatility')
    axes[i].legend()
    axes[i].grid(True, alpha=0.3)
    axes[i].tick_params(axis='x', rotation=45)

plt.tight_layout()
plt.show()
No description has been provided for this image
In [79]:
# Vẽ biểu đồ radar cho so sánh tổng thể GARCH
def create_garch_radar_chart():
    # Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
    metrics = ['MAPE', 'RMSE', 'AIC', 'BIC', 'Test_Size']
    
    # Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
    data = {}
    for split in splits:
        mape_norm = 1 - (garch_splits_info[split]['mape'] - min([garch_splits_info[s]['mape'] for s in splits])) / \
                   (max([garch_splits_info[s]['mape'] for s in splits]) - min([garch_splits_info[s]['mape'] for s in splits]))
        
        rmse_norm = 1 - (garch_splits_info[split]['rmse'] - min([garch_splits_info[s]['rmse'] for s in splits])) / \
                   (max([garch_splits_info[s]['rmse'] for s in splits]) - min([garch_splits_info[s]['rmse'] for s in splits]))
        
        aic_norm = 1 - (garch_splits_info[split]['aic'] - min([garch_splits_info[s]['aic'] for s in splits])) / \
                  (max([garch_splits_info[s]['aic'] for s in splits]) - min([garch_splits_info[s]['aic'] for s in splits]))
        
        bic_norm = 1 - (garch_splits_info[split]['bic'] - min([garch_splits_info[s]['bic'] for s in splits])) / \
                  (max([garch_splits_info[s]['bic'] for s in splits]) - min([garch_splits_info[s]['bic'] for s in splits]))
        
        test_size_norm = (garch_splits_info[split]['test_size'] - min([garch_splits_info[s]['test_size'] for s in splits])) / \
                        (max([garch_splits_info[s]['test_size'] for s in splits]) - min([garch_splits_info[s]['test_size'] for s in splits]))
        
        data[split] = [mape_norm, rmse_norm, aic_norm, bic_norm, test_size_norm]
    
    # Tạo radar chart
    angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
    angles += angles[:1]  # Complete the circle
    
    fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
    
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
    
    for i, (split, values) in enumerate(data.items()):
        values += values[:1]  # Complete the circle
        ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
        ax.fill(angles, values, alpha=0.25, color=colors[i])
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(metrics)
    ax.set_ylim(0, 1)
    ax.set_title('So sánh tổng thể các Split GARCH (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
    ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
    ax.grid(True)
    
    plt.tight_layout()
    plt.show()

create_garch_radar_chart()
No description has been provided for this image

Kết luận cuối cùng - GARCH Model¶

Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình GARCH với split tỉ lệ dữ liệu tốt nhất đã được xác định.

Các yếu tố được xem xét cho GARCH:

  • MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán giá
  • RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
  • AIC (Akaike Information Criterion): Đánh giá model fit và complexity
  • BIC (Bayesian Information Criterion): Đánh giá model với penalty cho complexity
  • Volatility Forecasting: Khả năng dự đoán volatility (điểm mạnh của GARCH)
  • Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá

Ưu điểm của GARCH:

  • Mô hình hóa hiệu quả volatility clustering trong dữ liệu tài chính
  • Cung cấp forecast cho cả price và volatility
  • Phù hợp với tính chất heteroskedasticity của XRP
  • Cho phép risk assessment thông qua volatility predictions

Khuyến nghị sử dụng: Mô hình GARCH với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá và volatility XRP, đặc biệt hiệu quả cho short-term forecasting và risk management.